Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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)

Expand Down
29 changes: 28 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.

Expand Down
121 changes: 121 additions & 0 deletions examples/query-method-examples.http
Original file line number Diff line number Diff line change
@@ -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

<?xml version="1.0" encoding="UTF-8"?>
<query>
<filter field="status" value="active"/>
<filter field="category" value="books"/>
<limit>25</limit>
</query>

###

### 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"
}
}

###
10 changes: 10 additions & 0 deletions snippets/http.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": [
Expand Down
2 changes: 1 addition & 1 deletion src/utils/curlRequestParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
3 changes: 2 additions & 1 deletion src/utils/httpElementFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'));
Expand Down Expand Up @@ -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[] = [];
Expand Down
2 changes: 1 addition & 1 deletion src/utils/httpRequestParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion syntaxes/http.tmLanguage.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down