ChatGPT解决这个技术问题 Extra ChatGPT

HTTP POST with URL query parameters -- good idea or not?

I'm designing an API to go over HTTP and I am wondering if using the HTTP POST command, but with URL query parameters only and no request body, is a good way to go.

Considerations:

"Good Web design" requires non-idempotent actions to be sent via POST. This is a non-idempotent action.

It is easier to develop and debug this app when the request parameters are present in the URL.

The API is not intended for widespread use.

It seems like making a POST request with no body will take a bit more work, e.g. a Content-Length: 0 header must be explicitly added.

It also seems to me that a POST with no body is a bit counter to most developer's and HTTP frameworks' expectations.

Are there any more pitfalls or advantages to sending parameters on a POST request via the URL query rather than the request body?

Edit: The reason this is under consideration is that the operations are not idempotent and have side effects other than retrieval. See the HTTP spec:

In particular, the convention has been established that the GET and HEAD methods SHOULD NOT have the significance of taking an action other than retrieval. These methods ought to be considered "safe". This allows user agents to represent other methods, such as POST, PUT and DELETE, in a special way, so that the user is made aware of the fact that a possibly unsafe action is being requested. ... Methods can also have the property of "idempotence" in that (aside from error or expiration issues) the side-effects of N > 0 identical requests is the same as for a single request. The methods GET, HEAD, PUT and DELETE share this property. Also, the methods OPTIONS and TRACE SHOULD NOT have side effects, and so are inherently idempotent.

Why use POST at all if you are not going to provide data in the body?
Because the operation is not idempotent.
@Jared, notice that the word "REST" doesn't appear in this question from 2.5 years ago. :) The HTTP spec about idempotence applies regardless of what the flavor-of-the-month architecture is for web services. Luckily, the system that this API was designed to proxy for has been rendered obsolete anyway.
Because server logs don't record POST parameters, but they record query strings. It's much easier to run the series of requests without instrumenting it in the browser, and then look at the traceback, than it is to click through them. Also the API was not browser-to-server, but rather server-to-server. Most importantly, the whole affair was canned anyway. :)
For anyone else who doesn't know what idempotent means :| restapitutorial.com/lessons/idempotency.html

L
Luke Girvin

If your action is not idempotent, then you MUST use POST. If you don't, you're just asking for trouble down the line. GET, PUT and DELETE methods are required to be idempotent. Imagine what would happen in your application if the client was pre-fetching every possible GET request for your service – if this would cause side effects visible to the client, then something's wrong.

I agree that sending a POST with a query string but without a body seems odd, but I think it can be appropriate in some situations.

Think of the query part of a URL as a command to the resource to limit the scope of the current request. Typically, query strings are used to sort or filter a GET request (like ?page=1&sort=title) but I suppose it makes sense on a POST to also limit the scope (perhaps like ?action=delete&id=5).


I selected this answer for this particular case, but I think R. Bemrose's argument is compelling for public APIs.
I don't think his answer is strictly correct. If you know the URL parameters for your form post when the HTML page is sent to the client, you can tack those URL parameters on to the form's action attribute, otherwise JavaScript can set the URL parameters when the form is submitted.
how about a post of an xml file to a url with query parameters? is that possible?
Another example: the request data could be in the http entity, while the requested format of the response is passed in the query parameter (/action?response_format=json)
+1 learned something today. Delete has a technicality to being idempotent. If the object is actually deleted then you will get a not found 404 so the server will have the same state but the response will be different. See Cow Picture: restapitutorial.com/lessons/idempotency.html
L
Luke Girvin

Everyone is right: stick with POST for non-idempotent requests.

What about using both an URI query string and request content? Well it's valid HTTP (see note 1), so why not?!

It is also perfectly logical: URLs, including their query string part, are for locating resources. Whereas HTTP method verbs (POST - and its optional request content) are for specifying actions, or what to do with resources. Those should be orthogonal concerns. (But, they are not beautifully orthogonal concerns for the special case of ContentType=application/x-www-form-urlencoded, see note 2 below.)

Note 1: HTTP specification (1.1) does not state that query parameters and content are mutually exclusive for a HTTP server that accepts POST or PUT requests. So any server is free to accept both. I.e. if you write the server there's nothing to stop you choosing to accept both (except maybe an inflexible framework). Generally, the server can interpret query strings according to whatever rules it wants. It can even interpret them with conditional logic that refers to other headers like Content-Type too, which leads to Note 2:

Note 2: if a web browser is the primary way people are accessing your web application, and application/x-www-form-urlencoded is the Content-Type they are posting, then you should follow the rules for that Content-Type. And the rules for application/x-www-form-urlencoded are much more specific (and frankly, unusual): in this case you must interpret the URI as a set of parameters, and not a resource location. [This is the same point of usefulness Powerlord raised; that it may be hard to use web forms to POST content to your server. Just explained a little differently.]

Note 3: what are query strings originally for? RFC 3986 defines HTTP query strings as an URI part that works as a non-hierarchical way of locating a resource.

In case readers asking this question wish to ask what is good RESTful architecture: the RESTful architecture pattern doesn't require URI schemes to work a specific way. RESTful architecture concerns itself with other properties of the system, like cacheability of resources, the design of the resources themselves (their behavior, capabilities, and representations), and whether idempotence is satisfied. Or in other words, achieving a design which is highly compatible with HTTP protocol and its set of HTTP method verbs. :-) (In other words, RESTful architecture is not very presciptive with how the resources are located.)

Final note: sometimes query parameters get used for yet other things, which are neither locating resources nor encoding content. Ever seen a query parameter like 'PUT=true' or 'POST=true'? These are workarounds for browsers that don't allow you to use PUT and POST methods. While such parameters are seen as part of the URL query string (on the wire), I argue that they are not part of the URL's query in spirit.


P
Powerlord

You want reasons? Here's one:

A web form can't be used to send a request to a page that uses a mix of GET and POST. If you set the form's method to GET, all the parameters are in the query string. If you set the form's method to POST, all the parameters are in the request body.

Source: HTML 4.01 standard, section 17.13 Form Submission


That's a decent argument, but I think modern browser's Javascript implementations kind of make it a moot point. I'll think about it though -- it is compelling in a future-proofing kind of way. Just because I'm not using a form for it now doesn't mean I won't want to later.
Mixing GET with POST is just a really bad idea - terribly breaking HTTP and for no good reason.
That snippet doesn't appear on the page you linked to
Right, but the method attribute just defines how the "form data set" is included in the request. When the method is POST, there's no mention of changing the URI in the form's action. And any URI can of course already contain a query string part.
@Powerlord This is just wrong. Try setting up a form to POST with an action of eg. /Books?bookCode=1234. The web server will get POST form vars and a query string.
j
jro

From a programmatic standpoint, for the client it's packaging up parameters and appending them onto the url and conducting a POST vs. a GET. On the server-side, it's evaluating inbound parameters from the querystring instead of the posted bytes. Basically, it's a wash.

Where there could be advantages/disadvantages might be in how specific client platforms work with POST and GET routines in their networking stack, as well as how the web server deals with those requests. Depending on your implementation, one approach may be more efficient than the other. Knowing that would guide your decision here.

Nonetheless, from a programmer's perspective, I prefer allowing either a POST with all parameters in the body, or a GET with all params on the url, and explicitly ignoring url parameters with any POST request. It avoids confusion.


s
swizzcheez

I would think it could still be quite RESTful to have query arguments that identify the resource on the URL while keeping the content payload confined to the POST body. This would seem to separate the considerations of "What am I sending?" versus "Who am I sending it to?".


The question wasn't about REST.
@user359996 Not all HTTP APIs are RESTful. In fact, most APIs which claim that actually aren't. Also, fun fact, REST isn't HTTP-only either.
P
Pang

The REST camp have some guiding principles that we can use to standardize the way we use HTTP verbs. This is helpful when building RESTful API's as you are doing.

In a nutshell: GET should be Read Only i.e. have no effect on server state. POST is used to create a resource on the server. PUT is used to update or create a resource. DELETE is used to delete a resource.

In other words, if your API action changes the server state, REST advises us to use POST/PUT/DELETE, but not GET.

User agents usually understand that doing multiple POSTs is bad and will warn against it, because the intent of POST is to alter server state (eg. pay for goods at checkout), and you probably don't want to do that twice!

Compare to a GET which you can do as often as you like (idempotent).


The REST camp say you should use HTTP as defined in the HTTP spec. i.e. RFC2616 Nothing more, nothing less.
@Darrel Referring ibm.com/developerworks/webservices/library/ws-restful: REST asks developers to use HTTP methods explicitly and in a way that's consistent with the protocol definition. This basic REST design principle establishes a one-to-one mapping between create, read, update, and delete (CRUD) operations and HTTP methods. According to this mapping: To create a resource on the server, use POST. To retrieve a resource, use GET. To change the state of a resource or to update it, use PUT. To remove or delete a resource, use DELETE.
Sorry but that's just plain wrong. REST requires compliance to a uniform interface. If you are using HTTP then that uniform interface is defined in part by RFC 2616. In that spec, there is not a one-to-one mapping between create, read, update, and delete and the HTTP methods.
GET and DELETE map pretty well to Read and Delete in CRUD, but using PUT/POST for Update and Create is not as straight-forward. See stackoverflow.com/questions/630453/put-vs-post-in-rest
Looking back at this 6 years later, and given that the question has been viewed ~100k times, I feel its worth putting in a small update. Darrel is correct according to Fielding's definition of REST (ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm) - there is no mention of mapping HTTP verbs to CRUD. IBM's developer advice (link in comment above) reflects common practice in implementing RESTful API's, not Fielding's definition of REST.
Y
Ytsen de Boer

I find it perfectly acceptable to use query parameters on a POST end-point if they refer to an already-existing resource that must be updated through the POST end-point (not created).

For example:

POST /user_settings?user_id=4
{
  "use_safe_mode": 1
}

The POST above has a query parameter referring to an existing resource, mirroring the GET end-point definition to get the same resource.

The body parameter defines how to update the existing resource.

Edited:

I prefer this to having the path of the end-point to point directly to the already-existing recourse, like some suggest to do, like so:

POST /user_settings/4
{
...
}

The reason is three-fold:

I find it has better readability, since the query parameters are named, like "user_id" in the above, in stead of just "4". Usually, there is also a GET endpoint to get the same resource. In that case the path of the end-point and the query parameters will be the same and I like that symmetry. I find the nesting can become cumbersome and difficult to read in case multiple parameters are needed to define the already-existing resource:

POST /user_settings/{user_id}/{which_settings_id}/{xyz}/{abc}/ ...
{
...
}

I think the endpoint should better be 'users/4/settings' because those user-settings never exist without a user (as the name is saying).
I respect that.
POST is used to create resources. If you have a resource present why do you pass it in query param? POST should not have query param. You can implement the service to honor the query param, but this is against REST spec.
"Why do you pass it in a query param?" Because, like for GET requests, the query parameters are used to refer to an existing resource. The POST part, is used to create a new resource, being created with respect to the existing resource specified in the query parameters. This leads to the 3 benefits listed in my answer that I wanted to share here.
Addendum to my latest comment: POST is also used to update existing resources, not just to create them. In those specific cases, I think it is perfectly acceptable that the query parameters then specify the existing resource to update. Especially given the resulting symmetry with the GET query to retrieve the same resource.