DELETE request running twice - django

I'm running a site with Django and I only have access to the backend.
When I delete an item through the web interface (an alert) the DELETE request is run twice:
/alerts/[alert_id]
Request Method: DELETE
Status Code: 301 Moved Permanently
then
/alerts/[alert_id]/
Request Method: DELETE
Status Code: 204 No Content
Any thoughts as to where the issue could be or why it does this? Apparently the frontend only calls the request that ends with a slash (the second one)

My bad, the docs explained the problem, I just didn't know what I was looking for
if the request URL does not match any of the patterns in the URLconf and it doesn’t end in a slash, an HTTP redirect is issued to the same URL with a slash appended.

The HTTP 204 No Content success status response code indicates that the request has succeeded, but that the client doesn't need to go away from its current page. A 204 response is cacheable by default. An ETag header is included in such a response.
The common use case is to return 204 as a result of a PUT request, updating a resource, without changing the current content of the page displayed to the user.
Source file

Related

Django Dev Server Processing The Same Ajax Request Inconsistently

I'm submitting a POST using ajax clientside. It works fine whenever all the requests are valid. However, when I send a POST request to an invalid URL, and then follow it up with another POST request, regardless of whether it's valid, Django processes the request in a completely different manner. Before changing some code it was actually causing the server to crash.
Heres what happens in the Django server stdout when I send two invalid requests in a row:
Not Found: /profile/add_favorite
[01/Aug/2019 15:42:25] "POST /profile/add_favorite HTTP/1.1" 404 4132
Not Found: /profile/add_favorite
[01/Aug/2019 15:42:30] "title=Video+TitlePOST /profile/add_favorite HTTP/1.1" 404 4178
The second request looks ill-formatted, and it's of a different format than the previous request.
The request was sent using basic Ajax:
var conf = {
type: 'POST',
url: url,
data: {title: "Video Title"},
headers: {'X-CSRFToken': csrftoken},
}
$.ajax(conf)
And in the dev tools I checked that the requests were the same:
That's just the headers but the data is also the same. Anyways, the exact same code was used for both request. This only happens when I send a request to an invalid URL and then send another request after it, and if I reload the page after sending an invalid request and send another request that request will work like it should, but not without returning a messed up response first.
Behold: If I send a post request to an invalid URL and then reload the page in Chrome chrome will not send a valid GET request, instead it sends a request with the same prepended title, the same invalid format!
[01/Aug/2019 16:04:03] "title=Video TitleGET / HTTP/1.1" 403 2513
Then that's the issue:
Whenever I send an invalid url Ajax request from my browser, the next request to my server will be made invalid in the same way. I feel sure it's not a browser-side issue, because I can send an incorrect request from one window, and then the next request from a different window will be incorrect! This is the screen from that next request:
The CSRF token shouldn't even matter the first time a site is loaded, making this message nonsensical.
This is obviously an problem of incredible magnitude for my server, as any time a person sends a request to an invalid URL at my server then the next request will not process correctly.
This must be a bug with the Django development server? What could possibly cause this?

Cloudfront Lambda#edge set cookie on Viewer Request

Update: Collected my thoughts better
I'm generating a unique identifier (UUID) for each user in the Viewer Request Lambda, and then selecting a cached page to return based upon that UUID. This works.
Ideally, this user would always have the same UUID.
I must generate that UUID in the Viewer Request if it is not present in a cookie on that Viewer Request. I also need that UUID to be set as a cookie, which of course happens in the response not the request.
Without caching, my server simply handles taking a custom header and creating a Set-Cookie in the response header.
I am not finding a way to handle this if I want to cache the page. I can ignore the request header for caching and serve the correct cached page, but then the user does not persist that UUID as no cookie is set to be utilized in their next request.
Has anyone accomplished something like this?
Things I'm trying
There are a few angles I'm working on with this, but haven't been able to get to work yet:
Some sort of setting in Cloudfront I'm unaware of that handles the header or other data pass-through from Viewer Request to Viewer Response, which could be used in a second lambda in Cloudfront.
Modify the response object headers preemptively in the Viewer Request. I don't think this is possible, as they return headers are not yet created, unless there's some built-in Cloudfront methodology I'm missing.
An existing pass-through header of some sort, I don't know if that's even a thing since I'm not intimately familiar with this aspect of request-response handling, but worth a shot.
Possibly (haven't tried yet though) I could create the entire response object in the Client Request lambda and somehow serve the cached page from there, modifying the response headers then passing it into the callback method.
Tobin's answer actually works, but is not a solid solution. If the user is not storing or serving their cookies it becomes an infinite loop, plus I'd rather not throw a redirect up in front of all of my pages if I can avoid it
Somewhat-working concept
Viewer Request Lambda, when UUID not present in cookies, generates UUID
Viewer Request Lambda sets UUID in cookies on header in request object. Callback with updated request object passed in
Presence of UUID cookie busts Cloudfront cache
Origin Request Lambda is triggered with UUID present
Origin Request Lambda calls original request URL again via http.get with UUID cookie set (40KB limit makes doing this in the Viewer Request Lambda impractical)
Second scenario for Viewer Request Lambda, seeing UUID now present, strips the UUID cookie then continues the request normally
Second Origin Request if not yet cached - Cached response if cached, as cache-busting UUID is not present - returns actual page HTML to First Origin Request
First Origin Request receives response from http.get containing HTML
First Origin Request creates custom response object containing response body from http.get and Set-Cookie header set with our original UUID
Subsequent calls, having the UUID already set, will strip the UUID from the cookie (to prevent cache busting) and skip directly to the second-scenario in the Viewer Request Lambda which will directly load the cached version of the page.
I say "somewhat" because when I try to hit my endpoint, I get a binary file downloaded.
EDIT
This is because I was not setting the content-type header. I now have only a 302 redirect problem... if I overcome this I'll post a full answer.
Original question
I have a function on the Viewer Request that picks an option and sets some things in the request before it's retrieved from the cache or server.
That works, but I want it to remember that choice for future users. The thought is to simply set a cookie I can read the next time that user comes through. As this is on the Viewer Request and not the Viewer Response I haven't figured out how to make that happen, or if it even is possible via the Lambda itself.
Viewer Request ->
Lambda picks options (needs to set cookie) ->
gets corresponding content ->
returns to Viewer with set-cookie header intact
I have seen the examples and been able to set cookies successfully in the Viewer Response via a Lambda. That doesn't help me much as the decision needs to be made on the request. Quite unsurprisingly adding this code into the Viewer Request shows nothing in the response.
I would argue that the really correct way to set a nonexistent cookie would be to return a 302 redirect to the same URI with Set-Cookie, and let the browser redo the request. This probably would not have much of an impact since the browser can reuse the same connection to "follow" the redirect.
But if you insist on not doing it that way, then you can inject the cookie into the request with your Viewer Request trigger and then emit a Set-Cookie with the same value in your Viewer Response trigger.
The request object, in a viewer response event, can be found at the same place where it's found in the original request event, event.Records[0].cf.request.
In a viewer-response trigger, this part of the structure contains the "request that CloudFront received from the viewer and that might have been modified by the Lambda function that was triggered by a viewer request event."
Use caution to ensure that you handle the cookie header correctly. The Cookie request header requires careful and accurate manipulation because the browser can use multiple formats when multiple cookies exist.
Once upon a time, cookies were required to be sent as a single request header.
Cookie: foo=bar; buzz=fizz
Parse these by splitting the values on ; followed by <space>.
But the browser may also split them with multiple headers, like this:
Cookie: foo=bar
Cookie: buzz=fizz
In the latter case, the array event.Records[0].cf.request.headers.cookie will contain multiple members. You need to examine the value attribute of each object in that array, check for multiple values within each, as well as accommodating the fact that the array will be completely undefined (not empty) if no cookies exist.
Bonus: Here's a function I wrote, that I believe correctly handles all cases including the case where there are no cookies. It will extract the cookie with the name you are looking for. Cookie names are case-sensitive.
// extract a cookie value from request headers, by cookie name
// const my_cookie_value = extract_cookie(event.Records[0].cf.request.headers,'MYCOOKIENAME');
// returns null if the cookie can't be found
// https://stackoverflow.com/a/55436033/1695906
function extract_cookie(headers, cname) {
const cookies = headers['cookie'];
if(!cookies)
{
console.log("extract_cookie(): no 'Cookie:' headers in request");
return null;
}
// iterate through each Cookie header in the request, last to first
for (var n = cookies.length; n--;)
{
// examine all values within each header value, last to first
const cval = cookies[n].value.split(/;\ /);
const vlen = cval.length;
for (var m = vlen; m--;)
{
const cookie_kv = cval[m].split('=');
if(cookie_kv[0] === cname)
{
return cookie_kv[1];
}
} // for m (each value)
} // for n (each header)
// we have no match if we reach this point
console.log('extract_cookie(): cookies were found, but the specified cookie is absent');
return null;
}
Are you able to add another directory: with the first cookie setter request, return (from the lambda) a redirect which includes the cookie-set header, that redirects to your actual content?
OK, long way round but:
Take cookie instruction from the incoming request
Set this somewhere (cache, etc)
Let the request get your object
on the Response, also call a function that reads the (cache) and sets the set-cookie header on the response if needed?
It's been more than one year since the question was published. I hope you found a solution and you can share it with us!
I am facing the same problem and I've thinking also about the infinite loop... What about this?
The viewer request event sends back a 302 response with the cookie set, e.g. uuid=whatever and a GET parameter added to the URL in the Location header, e.g. _uuid_set_=1.
In the next viewer request where the GET parameter _uuid_set_ is set (and equals 1, but this is not needed), there will be two options:
Either the cookie uuid is not set, in which case you can send back a response 500 to break the loop, or whatever fits your needs,
or the cookie is set, in which case you send another 302 back with the parameter _uuid_set_ removed, so that it is never seen by the end user and cannot be copy-pasted and shared and we all can sleep at night.

Post request in Postman - Content Length Issue

Using Postman Version 6.4.4 (6.4.4), on MAC, i am not able to run any of the post request which was working for previous releases.
I keep getting the following similar exception from my middleware. THe issue is not with my middleware as the same request are working when invoked from my app.
The value 146 in the Content-Length request header is > than the
actual number of bytes in request body. Resend the request with the
correct content length
I tried to add 'Content-Length' to the header with some values < what is returned in the exception, in which case the request goes through but the request body is always empty.

Will amazon-cloudfront follow a 308 redirect?

I am working on setting up amazon cloud front and have been playing around with the cache-control settings. It is explicitly stated here, that amazon-cloudfront will not follow a 301 and 307.
I am wondering if there is documentation on if the redirect will be followed if the origin returns a 308?
It seems a little strange that 302 and 308 are not mentioned, here, but CloudFront does not follow redirects. They are stored in the cache and returned to the browser.
You can intercept redirects with a Lambda#Edge response trigger, but the typical application for this is to rewrite the Location header and send the browser somewhere other than where it would otherwise have gone.
For small responses, it's possible to actually follow the redirect using an Origin Response trigger that makes a request using the Node HTTP client, but this only suports responses up to 1 MB in total size, and would probably not perform as well as simply letting the browser follow the redirect.
There is some additional documentation here of handling redirects:
If you change the location of an object on the origin server, you can
configure your web server to redirect requests to the new location.
After you configure the redirect, the first time a viewer submits a
request for the object, CloudFront Front sends the request to the
origin, and the origin responds with a redirect (for example, 302
Moved Temporarily). CloudFront caches the redirect and returns it to
the viewer. CloudFront does not follow the redirect.
You can configure your web server to redirect requests to one of the
following locations:
The new URL of the object on the origin server. When the viewer follows the redirect to the new URL, the viewer bypasses CloudFront
and goes straight to the origin. As a result, we recommend that you
not redirect requests to the new URL of the object on the origin.
The new CloudFront URL for the object. When the viewer submits the request that contains the new CloudFront URL, CloudFront gets the
object from the new location on your origin, caches it at the edge
location, and returns the object to the viewer. Subsequent requests
for the object will be served by the edge location. This avoids the
latency and load associated with viewers requesting the object from
the origin. However, every new request for the object will incur
charges for two requests to CloudFront.
But because 301 and 308 only difference is that 308
does not allow changing the request method from POST to GET
i would guess that it is handled like 301.
The documentation has since original post been updated to include info on 301, 302, 304, 307 and 308.

Ember Data sending OPTIONS requests after save

After sucessfully updating an ember-data model, ember-data sends an OPTIONS request.
First of all, I would like to know why it sends an OPTIONS request.
And the actual problem is, that this options request is sent to the "location" where the put request returned from. Which is problematic because, I am using grunt-proxy and forward my requests to another domain. But the response from my PUT of course has this "other" domain as location. And the OPTIONS request ist then sent directly to this domain, which is of course not working due to CORS requests.
Additional Comment:
I found out that my PUT request returns a 302 Temporarily moved. I have to be honest, I have no idea, why because when I request my put Endpoint directly with some data, I never get a 302, always 204.
Could this be the reason why ember-data making the OPTIONS request?