feat(http): add cURL tab to View Results Tree Request panel - #6736
feat(http): add cURL tab to View Results Tree Request panel#6736poliakov-alex wants to merge 3 commits into
Conversation
Render the sampled HTTP request as a ready-to-run curl command in a new "cURL" sub-tab next to Raw and HTTP, so it can be copied to a console or shared. Closes apache#6375 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
vlsi
left a comment
There was a problem hiding this comment.
Adversarial pass over the cURL tab, on top of @milamberspace's review. I built the branch, ran the existing tests (green), and exercised buildCurlCommand with a probe test plus real curl against a local HTTP server.
The feature is worth having and the SPI integration is clean. Requesting changes for one class of problem: several ordinary samples render a command that looks correct but reproduces a different request from the one JMeter sent. Four inline notes fall into that class.
- Repeated header names are collapsed by
parseHeaders(RequestViewCurl#119). - The
X-LocalAddresspseudo-header is copied into the command although it never went on the wire (RequestViewCurl#57). HEADsamples rendercurl -X 'HEAD', which fails or hangs (RequestViewCurl#107).- Rendered-placeholder bodies reach
--data-rawon two non-multipart paths, so the multipart fix @milamberspace asked for will not cover them (RequestViewCurl#139).
The remaining notes are suggestions, not conditions.
Checked and found correct, for the record: single-quote escaping (name=O'Brien reached the server as exactly 12 bytes), -b cookie syntax, @AutoService registration (both views land in META-INF/services), message-key ordering, locale fallback through the parent bundle, and redirect handling (HTTPSamplerBase takes URL, method, headers, and body from the last hop consistently).
milamberspace
left a comment
There was a problem hiding this comment.
Thanks for this, @poliakov-alex — really nice work. CurlCommandFormatter is kept Swing-free and reusable, the test coverage on the tricky cases is excellent, and I appreciate the care around the curl pitfalls (--head vs -X HEAD, -X GET to keep the method, --data-raw instead of --data, hop-by-hop headers dropped, and the security note in the docs).
One correctness issue I'd like fixed before merge, plus a couple of minor notes.
Must fix — regular multipart fields should use --form-string
In parseMultipartForm the non-file fields are emitted as -F 'name=value':
parts.add(argument.getName() + "=" + argument.getValue()); // -> -F 'name=value'curl gives special meaning to a -F value that starts with @ or <: @ reads a file to upload, < reads a file's contents as the field value. So a legitimate text field whose value begins with @ or < (e.g. handle=@someuser, xml=<root/>) is silently misinterpreted — curl tries to open a file, or fails. Shell single-quoting does not protect against this, because it is curl itself (not the shell) doing the interpretation.
Please emit regular fields with --form-string 'name=value' (verbatim value, no @/< handling) and keep -F 'name=@path;type=...' only for the file parts. This mirrors the reasoning you already applied when choosing --data-raw over --data.
Minor (non-blocking)
- Redundant
--compressed+ explicitAccept-Encoding: when--compressedis emitted, curl sends its ownAccept-Encoding, so the explicit-H 'Accept-Encoding: ...'is redundant. Harmless, but you could drop the header when emitting--compressed. - Tab order: only the Raw tab is pinned first in
RequestPanel; the HTTP↔cURL order comes fromServiceLoaderand isn't deterministic, so "next to Raw and HTTP" is best-effort. Nothing to change in the PR — just noting it. - PR description nit: the description mentions
RequestViewCurlTest(8 tests) but the file isCurlCommandFormatterTest(15 tests).
Once the --form-string change is in (ideally with a test for a field value starting with @), this looks good to merge.
Added commit, please check |
Render the sampled HTTP request as a ready-to-run
curlcommand in a new "cURL" sub-tab next to Raw and HTTP, so it can be copied to a console or shared with a developer.Closes #6375
Description
Adds a cURL tab to the Request panel of the View Results Tree listener, next to the existing Raw and HTTP tabs. It renders the sampled HTTP request as a ready-to-run
curlcommand.What the generated command contains:
--headforHEAD(a plain-X HEADmakes curl wait for a body it never receives); no-Xfor a plainGET;-X 'GET'only when a GET carries a body (otherwise curl switches it to POST);-Xfor every other method.-H). Headers are split line by line so repeated header names (e.g. severalAcceptvalues) are all preserved.-b(JMeter tracks them separately from the header list).--data-raw(verbatim, no@/<interpretation);--form-string 'field=value'for text fields and-F 'name=@filename;type=...'for files. Since JMeter does not keep the uploaded bytes, the file is referenced as@filename— a placeholder to edit to a real path before running. The originalContent-Typeis dropped so curl sets its own multipart boundary;--compressedwhen the request setAccept-Encoding(HttpClient disables automatic decompression, so it is only present when explicitly set); the explicit header is then dropped as redundant.Headers that curl manages itself or that never went on the wire are omitted, so the command actually runs:
Content-Length,Connection,Keep-Alive,Proxy-Connection,Transfer-Encoding,Upgrade(e.g.Connectionis forbidden in HTTP/2 and yieldscurl: (92) ... PROTOCOL_ERROR; a manualContent-Lengthconflicts with the body curl computes), and theX-LocalAddresspseudo-header JMeter adds only for reporting.Values are single-quoted and shell-escaped, so the output is paste-safe in a POSIX-compatible shell (it is not
cmd.exe/ PowerShell syntax — noted in the docs). The command reproduces whatever the sample carried, includingAuthorizationheaders, cookies and API keys, so it is documented as sensitive when shared.The tab is contributed through the existing
RequestViewservice interface (@AutoService), so no wiring changes were needed inRequestPanel. The command builder lives inorg.apache.jmeter.protocol.http.curl.CurlCommandFormatter(next toBasicCurlParser) and has no Swing dependency, so it can be reused later — e.g. by a "Copy as cURL" action on the sampler itself, which would also have the real file paths for uploads.Motivation and Context
Fixes #6375. Users frequently need to reproduce a sampled request outside JMeter — in a terminal or when handing it to a developer. Today they must reconstruct the
curlcommand by hand from the Raw/HTTP tabs.How Has This Been Tested?
CurlCommandFormatterTest(17 JUnit tests) covering: plain GET (no-X), GET with a body, POST with headers and body, repeated header names, skipped connection/auto/X-LocalAddressheaders,HEAD→--head, multipart rebuilt as--form-string/-Fwith the file name, a text field whose value starts with@(must stay--form-string), file-as-body and non-repeatable placeholders omitted, whitespace-only body kept, cookies,Accept-Encoding→--compressed, single-quote escaping, null URL, and two round-trips that feed the generated command back throughBasicCurlParserand compare method / URL / headers / body../gradlew :src:protocol:http:test,checkstyleMain,checkstyleTest,autostyleJavaCheck— all green.Screenshots (if appropriate):
Types of changes
New feature (non-breaking change which adds functionality)
Checklist: