Issue summary updated: comment#33
Problem/Motivation
According to the HTTP Spec (RFC 7231 http://tools.ietf.org/html/rfc7231), response code 303 was designed for use in redirecting to a GETtable resource after a POST.
Excerpt from RFC7231 (Section 4.3.3):
If the result of processing a POST would be equivalent to a
representation of an existing resource, an origin server MAY redirect
the user agent to that resource by sending a 303 (See Other) response
with the existing resource's identifier in the Location field.
Proposed resolution
* in FormSubmitter::redirectForm(), use Response::SEE_OTHER as returned status code
Remaining tasks
User interface changes
API changes
Original report by @urbanape
This issue was brought to our attention by a CDN's strict adherence to the spec, where subsequent GETs from a 302 from the same URL were causing cache problems.
Relevant sections from RFC 2616:
9.5 POST
The POST method is used to request that the origin server accept the
entity enclosed in the request as a new subordinate of the resource
identified by the Request-URI in the Request-Line. POST is designed
to allow a uniform method to cover the following functions:
- Annotation of existing resources;
- Posting a message to a bulletin board, newsgroup, mailing list,
or similar group of articles;
- Providing a block of data, such as the result of submitting a
form, to a data-handling process;
- Extending a database through an append operation.
The actual function performed by the POST method is determined by the
server and is usually dependent on the Request-URI. The posted entity
is subordinate to that URI in the same way that a file is subordinate
to a directory containing it, a news article is subordinate to a
newsgroup to which it is posted, or a record is subordinate to a
database.
The action performed by the POST method might not result in a
resource that can be identified by a URI. In this case, either 200
(OK) or 204 (No Content) is the appropriate response status,
depending on whether or not the response includes an entity that
describes the result.
If a resource has been created on the origin server, the response
SHOULD be 201 (Created) and contain an entity which describes the
status of the request and refers to the new resource, and a Location
header (see section 14.30).
Responses to this method are not cacheable, unless the response
includes appropriate Cache-Control or Expires header fields. However,
the 303 (See Other) response can be used to direct the user agent to
retrieve a cacheable resource.
POST requests MUST obey the message transmission requirements set out
in section 8.2.
See section 15.1.3 for security considerations.
10.3.3 302 Found
The requested resource resides temporarily under a different URI.
Since the redirection might be altered on occasion, the client SHOULD
continue to use the Request-URI for future requests. This response
is only cacheable if indicated by a Cache-Control or Expires header
field.
The temporary URI SHOULD be given by the Location field in the
response. Unless the request method was HEAD, the entity of the
response SHOULD contain a short hypertext note with a hyperlink to
the new URI(s).
If the 302 status code is received in response to a request other
than GET or HEAD, the user agent MUST NOT automatically redirect the
request unless it can be confirmed by the user, since this might
change the conditions under which the request was issued.
Note: RFC 1945 and RFC 2068 specify that the client is not allowed
to change the method on the redirected request. However, most
existing user agent implementations treat 302 as if it were a 303
response, performing a GET on the Location field-value regardless
of the original request method. The status codes 303 and 307 have
been added for servers that wish to make unambiguously clear which
kind of reaction is expected of the client.
10.3.4 303 See Other
The response to the request can be found under a different URI and
SHOULD be retrieved using a GET method on that resource. This method
exists primarily to allow the output of a POST-activated script to
redirect the user agent to a selected resource. The new URI is not a
substitute reference for the originally requested resource. The 303
response MUST NOT be cached, but the response to the second
(redirected) request might be cacheable.
The different URI SHOULD be given by the Location field in the
response. Unless the request method was HEAD, the entity of the
response SHOULD contain a short hypertext note with a hyperlink to
the new URI(s).
Note: Many pre-HTTP/1.1 user agents do not understand the 303
status. When interoperability with such clients is a concern, the
302 status code may be used instead, since most user agents react
to a 302 response as described here for 303.
| Comment | File | Size | Author |
|---|---|---|---|
| #40 | 355157-40.patch | 4.21 KB | valthebald |
| #38 | 355157-38.patch | 3.73 KB | valthebald |
| #33 | 355157-33.patch | 3.02 KB | valthebald |
| #30 | rfc2616-303-redirect-355157-30.patch | 1.83 KB | valthebald |
| #10 | rfc2616-303-redirect-355157-10.patch | 1.23 KB | valthebald |
Comments
Comment #1
febbraro commentedsubscribing
Comment #2
urbanape commentedActually, the patch needs a little more work when applying additional extraneous query string params when in the presence of pre-existing query strings.
Comment #3
urbanape commentedUpdated patch builds the action URL back up from constituent pieces, if a query string already exists.
Comment #4
brianV commentedBumping to 7.X where this should be implemented, and backported to 6.X if necessary.
Comment #5
lilou commentedYou should add a comment referring to RFC.
Comment #6
damien tournoud commentedWhy adding form_post=1 to the action URL?
Comment #7
effulgentsia commentedThanks for bringing this issue up!
I have the same question as #6: why is any change to the form action url needed? Is there something in the spec that says a 303 must return a different URL? Why can't it return a redirect to the same URL the way that a 302 does?
I'm concerned about this. Does this mean that by issuing a 303, we become incompatible with pre-HTTP/1.1 user agents and is that a problem? However, if we're currently incompatible with CDNs because we're not implementing HTTP/1.1 correctly, then I do think we need to do something, we just may need more logic here to switch the response code based on user agent.
Comment #8
effulgentsia commentedPlease correct issue title if I didn't get it quite right.
Comment #9
damien tournoud commentedWell, we should issue 303, because it was designed to do so. On the other hand, I really doubt that this is causing issue with CDNs, I think the original poster was just confused (the extraneous ?form_post=1 is probably the part that fix the CDN issue for him/her).
Comment #10
valthebaldI think at the time D8 is released, concern on non-HTTP/1.1 will be even less relevant than today, so:
Comment #12
valthebaldFixing obvious syntax error
Comment #13
damien tournoud commentedThis needs to be smarter, as we actually support all of the drupal_goto() parameters in
$form_state['redirect'], including the response code.I recommend something along the lines of:
Comment #14
valthebaldIn case $form_state value is checked somewhere after drupal_goto() call, I placed drupal_goto parameters in a new variable.
Also, I had to modify simpletest.test to expect 303 redirect
Comment #15
c960657 commentedHow about changing the default HTTP response code for drupal_goto() to 303? It seems that 303 is a more appropriate status code in the most common use-cases, also when redirecting a GET request.
For compatibility with HTTP/1.0 clients we can make the default 302 on HTTP/1.0 and 303 in newer clients. Note that even though HTTP/1.1 is almost 13 years old, there are still quite a few HTTP/1.0 clients around, including our own client in drupal_http_request().
Comment #16
valthebald303 code should be issued only as a response to POST-ed form submission.
drupal_goto() is used for other use cases as well (path redirects, redirects based on browser languages etc.), so - no, default status code should remain 302, and this is ok
Comment #17
c960657 commentedThat is not what the RFC says, is it?
AFAICT the semantics of 302 and 303 is almost the same. 302 seems to indicate that the resource has been moved temporarily (i.e. for some period of time) while 303 seems to be relevant in the case where the user is redirected due to some dynamic circumstances, i.e. when requesting the same URL again immediately after does not necessarily redirect to the same place.
If we cannot agree on changing the default status code for all requests, I suggest we should do it at least during POST requests. As described in this ticket, there is almost never a good reason for responding with 302 to a POST request, except for compatibility with HTTP/1.0 clients.
Comment #18
valthebaldPp. 10.3.4 303 See Other:
303 was not designed to replace 302, these status codes just have different meanings.
Comment #19
c960657 commentedThe quoted part of the RFC does not forbid or advice against the use of 303 in GET request.
I know that 302 and 303 have different meanings (my last post was an attempt to explain the difference as far as I interpret the RFC), but I was arguing that 303 may be more relevant in many use cases, even in GET requests.
Comment #20
valthebaldCan you explain mentioned use cases? I still can't get why you suggest to change default status code of drupal_goto()
Comment #21
c960657 commentedIn my interpretation of the HTTP spec, the two status codes have these meanings (roughly speaking):
Note in particular this sentence in the spec: .
302 generally requires a “closer relationship” between the source and destination URLs than 303.
I looked through Drupal core to find examples of where drupal_goto() is used in GET requests. Here are some typical use-cases (not a complete list):
Likewise in comment_reply() that handles comment/reply/%node/%parent_comment. The function does various sanity checks (e.g. does the %node and %parent_comment match — if not, it really should return 404, because the URL has never been valid) and access control checks (e.g. is the user authorized to post comments etc. — if not, it really should return 403), and if any of these fail, it calls drupal_set_message() and redirect to node/%node. Also here 303 is a good match.
Note that I am not arguing that we should actually send 403 or 404 in these cases. AFAIK these codes cannot be combined with a Location redirect header, so a 3xx code is probably better.
Comment #22
kscheirer#14: rfc2616-303-redirect-355157-14.patch queued for re-testing.
Comment #23
kscheirerRetesting against latest HEAD since it has been over a year.
Comment #25
valthebald@kscheirer: Thanks for bringing this back to life
$_GET['q'] has gone since the last testbot run, + multiple other changes
Rerolling
Comment #27
valthebaldAnother try
Comment #28
valthebaldAs a side note, patch in #27 contains fix in EntityTranslationController.php, which used $form_state['redirect'] in a weird way:
As far as I see, this is the only place in the core source, where $form_state['redirect'] is a one-element associative array.
call_user_func_array() expects indexed, not associative array.
This works (due to all-forgiving PHP nature), but only until you don't attempt to modify $form_state['redirect']
Comment #29
Crell commentedPretty sure this no longer applies... :-) Still worth doing though.
Comment #30
valthebaldRerolling
Comment #31
sunWould be good to add some explicit test coverage for the expected response code to one of the existing Form API tests.
Not sure whether this can be tested in phpunit. A WebTest needs to temporarily set/override
$this->additionalCurlOptionsto disablecurlExec()'s automated following of HTTP redirects, so you can$this->assertResponse()the response prior to the redirect.Comment #32
Crell commentedLet's reference the new RFC instead. Also, the Response class has constants for all of the legal return codes. Let's use those to make the code a bit more self-documenting. (Response::HTTP_SEE_OTHER in this case)
Comment #33
valthebaldAdded reference to the new RFC, plus replaced constants with Response::SEE_OTHER.
@sun: returned status code is already tested, can this be a followup issue?
Comment #34
valthebaldComment #35
valthebaldComment #36
valthebaldComment #38
valthebaldAs it turns out, there *is* explicit test for return status (ammended FormSubmitterTest)
Comment #40
valthebaldMy bad
Comment #41
sunThanks!
The inline comment reads a bit lengthy/wordy and could be shortened to just one line, but ok.
Comment #42
alexpottCommitted f531d9d and pushed to 8.x. Thanks!