From f3a45a1cebe89829e92de4000f76266a021002f6 Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Wed, 6 May 2026 16:07:52 -0500 Subject: [PATCH] Add support for HTTP QUERY method --- CHANGELOG.md | 3 + README.md | 29 ++++++- examples/query-method-examples.http | 121 ++++++++++++++++++++++++++++ snippets/http.json | 10 +++ src/utils/curlRequestParser.ts | 2 +- src/utils/httpElementFactory.ts | 3 +- src/utils/httpRequestParser.ts | 2 +- syntaxes/http.tmLanguage.json | 2 +- 8 files changed, 167 insertions(+), 5 deletions(-) create mode 100644 examples/query-method-examples.http diff --git a/CHANGELOG.md b/CHANGELOG.md index 97f68de8..d1afbd64 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## Unreleased +* __Feature__: Add support for HTTP QUERY method as defined in [draft-ietf-httpbis-safe-method-w-body](https://www.ietf.org/archive/id/draft-ietf-httpbis-safe-method-w-body-05.html) + ## 0.25.1 (2022/07/06) * __Bug Fix__: [Fix request parse error for curl request without body](https://github.com/Huachao/vscode-restclient/issues/1026) diff --git a/README.md b/README.md index 5110daa3..2648e0cb 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ REST Client allows you to send HTTP request and view the response in Visual Stud - Auto completion for method, url, header, custom/system variables, mime types and so on - Comments (line starts with `#` or `//`) support - Support `json` and `xml` body indentation, comment shortcut and auto closing brackets - - Code snippets for operations like `GET` and `POST` + - Code snippets for operations like `GET`, `POST`, and `QUERY` - Support navigate to symbol definitions(request and file level custom variable) in open `http` file - CodeLens support to add an actionable link to send request - Fold/Unfold for request block @@ -223,6 +223,33 @@ name=foo > When your mouse is over the document link, you can `Ctrl+Click`(`Cmd+Click` for macOS) to open the file in a new tab. +## Making QUERY Requests +The HTTP `QUERY` method is a safe, idempotent method that allows sending a request body, similar to `GET` but with the ability to include complex query parameters in the body. This is useful when query parameters are too complex or long to fit in the URL. + +The `QUERY` method is defined in [draft-ietf-httpbis-safe-method-w-body](https://www.ietf.org/archive/id/draft-ietf-httpbis-safe-method-w-body-05.html). + +Here's an example of using the `QUERY` method: +```http +QUERY https://api.example.com/search HTTP/1.1 +Content-Type: application/json + +{ + "filters": { + "status": "active", + "category": "electronics" + }, + "pagination": { + "page": 1, + "limit": 10 + } +} +``` + +The `QUERY` method is ideal when: +- You need to send complex query parameters that don't fit in a URL +- The request is safe (no side effects) and idempotent +- You want the benefits of cacheability that `GET` provides + ## Making GraphQL Request With [GraphQL](https://www.graphql.com/) support in REST Client extension, you can author and send `GraphQL` query using the request body. Besides that you can also author GraphQL variables in the request body. GraphQL variables part in request body is optional, you also need to add a **blank line** between GraphQL query and variables if you need it. diff --git a/examples/query-method-examples.http b/examples/query-method-examples.http new file mode 100644 index 00000000..d4ba657f --- /dev/null +++ b/examples/query-method-examples.http @@ -0,0 +1,121 @@ +### +# HTTP QUERY Method Examples +# This file demonstrates using the HTTP QUERY method as defined in: +# https://www.ietf.org/archive/id/draft-ietf-httpbis-safe-method-w-body-05.html +# +# The QUERY method is used for safe, idempotent requests that include +# a request body, similar to GET but with a body. +### + +############################################################################### +# Basic QUERY Examples +############################################################################### + +### QUERY with JSON body +# The QUERY method allows sending a request body for complex queries +# while maintaining the safe and idempotent semantics of GET + +QUERY https://httpbin.org/anything HTTP/1.1 +Content-Type: application/json + +{ + "filters": { + "status": "active", + "category": "electronics" + }, + "pagination": { + "page": 1, + "limit": 10 + }, + "sort": { + "field": "created_at", + "order": "desc" + } +} + +### + +### QUERY with XML body +QUERY https://httpbin.org/anything HTTP/1.1 +Content-Type: application/xml + + + + + + 25 + + +### + +### QUERY with URL-encoded form data +QUERY https://httpbin.org/anything HTTP/1.1 +Content-Type: application/x-www-form-urlencoded + +search=keyword&category=all&page=1 + +### + +### QUERY with Accept header +QUERY https://httpbin.org/anything HTTP/1.1 +Content-Type: application/json +Accept: application/json + +{ + "query": "SELECT * FROM items WHERE active = true" +} + +### + +### QUERY with Authorization +@token = your-bearer-token + +QUERY https://httpbin.org/anything HTTP/1.1 +Content-Type: application/json +Authorization: Bearer {{token}} + +{ + "query": { + "match": { + "title": "REST Client" + } + } +} + +### + +############################################################################### +# QUERY vs POST comparison +############################################################################### + +### Using QUERY (safe, idempotent - can be cached) +# QUERY is appropriate when: +# - The request is safe (no side effects) +# - The request is idempotent +# - You need to send complex query parameters in a body + +QUERY https://httpbin.org/anything HTTP/1.1 +Content-Type: application/json + +{ + "search": "complex query that doesn't fit in URL" +} + +### + +### Using POST (not safe, potentially not idempotent) +# POST should be used when: +# - The request causes side effects +# - The request creates/modifies resources + +POST https://httpbin.org/anything HTTP/1.1 +Content-Type: application/json + +{ + "action": "create", + "data": { + "name": "New Item" + } +} + +### diff --git a/snippets/http.json b/snippets/http.json index 23c125b3..4d19e4b7 100644 --- a/snippets/http.json +++ b/snippets/http.json @@ -33,6 +33,16 @@ ], "description": "PUT Request" }, + "Query request block": { + "prefix": "query", + "body": [ + "QUERY ${1:url} HTTP/1.1", + "${2:header name}: ${3:header value}", + "", + "${4:content}" + ], + "description": "QUERY Request" + }, "GraphQL request block": { "prefix": "graphql", "body": [ diff --git a/src/utils/curlRequestParser.ts b/src/utils/curlRequestParser.ts index c788fb78..2641ee20 100644 --- a/src/utils/curlRequestParser.ts +++ b/src/utils/curlRequestParser.ts @@ -19,7 +19,7 @@ export class CurlRequestParser implements RequestParser { let requestText = CurlRequestParser.mergeMultipleSpacesIntoSingle( CurlRequestParser.mergeIntoSingleLine(this.requestRawText.trim())); requestText = requestText - .replace(/(-X)(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS|CONNECT|TRACE|LOCK|UNLOCK|PROPFIND|PROPPATCH|COPY|MOVE|MKCOL|MKCALENDAR|ACL|SEARCH)/, '$1 $2') + .replace(/(-X)(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS|CONNECT|TRACE|LOCK|UNLOCK|PROPFIND|PROPPATCH|COPY|MOVE|MKCOL|MKCALENDAR|ACL|SEARCH|QUERY)/, '$1 $2') .replace(/(-I|--head)(?=\s+)/, '-X HEAD'); const parsedArguments = yargsParser.default(requestText); diff --git a/src/utils/httpElementFactory.ts b/src/utils/httpElementFactory.ts index 56690fec..4db32ac5 100644 --- a/src/utils/httpElementFactory.ts +++ b/src/utils/httpElementFactory.ts @@ -23,6 +23,7 @@ export class HttpElementFactory { originalElements.push(new HttpElement('OPTIONS', ElementType.Method)); originalElements.push(new HttpElement('TRACE', ElementType.Method)); originalElements.push(new HttpElement('CONNECT', ElementType.Method)); + originalElements.push(new HttpElement('QUERY', ElementType.Method)); // add http headers originalElements.push(new HttpElement('Accept', ElementType.Header, null, 'Specify certain media types which are acceptable for the response')); @@ -223,7 +224,7 @@ export class HttpElementFactory { return; } const prefixLength = protocol.length + 2; // https: + // - originalElements.push(new HttpElement(`${requestUrl.substr(prefixLength)}`, ElementType.URL, '^\\s*(?:(?:GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS|CONNECT|TRACE|LOCK|UNLOCK|PROPFIND|PROPPATCH|COPY|MOVE|MKCOL|MKCALENDAR|ACL|SEARCH)\\s+)https?\\:\\/{2}')); + originalElements.push(new HttpElement(`${requestUrl.substr(prefixLength)}`, ElementType.URL, '^\\s*(?:(?:GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS|CONNECT|TRACE|LOCK|UNLOCK|PROPFIND|PROPPATCH|COPY|MOVE|MKCOL|MKCALENDAR|ACL|SEARCH|QUERY)\\s+)https?\\:\\/{2}')); }); let elements: HttpElement[] = []; diff --git a/src/utils/httpRequestParser.ts b/src/utils/httpRequestParser.ts index 1a247690..c1a3dcfc 100644 --- a/src/utils/httpRequestParser.ts +++ b/src/utils/httpRequestParser.ts @@ -152,7 +152,7 @@ export class HttpRequestParser implements RequestParser { let url: string; let match: RegExpExecArray | null; - if (match = /^(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS|CONNECT|TRACE|LOCK|UNLOCK|PROPFIND|PROPPATCH|COPY|MOVE|MKCOL|MKCALENDAR|ACL|SEARCH)\s+/i.exec(line)) { + if (match = /^(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS|CONNECT|TRACE|LOCK|UNLOCK|PROPFIND|PROPPATCH|COPY|MOVE|MKCOL|MKCALENDAR|ACL|SEARCH|QUERY)\s+/i.exec(line)) { method = match[1]; url = line.substr(match[0].length); } else { diff --git a/syntaxes/http.tmLanguage.json b/syntaxes/http.tmLanguage.json index 3b0544cc..74089e1a 100644 --- a/syntaxes/http.tmLanguage.json +++ b/syntaxes/http.tmLanguage.json @@ -258,7 +258,7 @@ ] } }, - "match": "(?i)^(?:(get|post|put|delete|patch|head|options|connect|trace|lock|unlock|propfind|proppatch|copy|move|mkcol|mkcalendar|acl|search)\\s+)\\s*(.+?)(?:\\s+(HTTP\\/\\S+))?$", + "match": "(?i)^(?:(get|post|put|delete|patch|head|options|connect|trace|lock|unlock|propfind|proppatch|copy|move|mkcol|mkcalendar|acl|search|query)\\s+)\\s*(.+?)(?:\\s+(HTTP\\/\\S+))?$", "name": "http.requestline" }, "response-line": {