Right way to enable requests with method DELETE in nginx - rest

I was writing a RESTful applicaiton in PHP and enabled DELETE, PUT requests for nginx.
location / {
root html;
index index.php index.html index.htm;
dav_methods PUT DELETE;
}
When I executed a REST Request with method DELETE, which I wanted to handle inside my index.php - nginx removed the html folder.
What is the right way to tell nginx to pass DELETE requests to my index.php ?

Nginx does not disable PUT or DELETE requests, but it does not allow these requests on a folder index. There isn't really anything that needs to be enabled with nginx (you should remove the dav_methods line), but you need to avoid accessing your index.php through the index directive like:
index index.php index.html index.htm;
Instead use try_files to match the index.php file ie like:
try_files $uri /index.php$is_args$args;
In this case nginx wont complain about your DELETE method.

NginX executes those HTTP methods (DELETE, PUT) directly without even invoking PHP engine because they are handled by the DAV extension inside nginX.
To overcome the issue, you may use POST HTTP method for all of your API calls, but add additional custom header to indicate the actual REST method - instead of this
PUT /api/Person/4 HTTP/1.1
Host: localhost:10320
Content-Type: application/json
Cache-Control: no-cache
you will invoke this
POST /api/Person/4 HTTP/1.1
Host: localhost:10320
Content-Type: application/json
X-REST-Method: PUT
Cache-Control: no-cache
and then in PHP you will check in this way
if($_SERVER['HTTP_X_REST_METHOD']!='')
switch($_SERVER['HTTP_X_REST_METHOD'])
{
case 'PUT':
...
break;
case 'PATCH':
...
break;
case 'DELETE':
...
break;
}

Related

Files served with Aqueduct don't have a Content-Length header

I am writing a backend for my Flutter app using Aqueduct. I have Aqueduct set up so that Nginx proxies requests to it like this:
server {
root /home/web/my_server/web;
index index.html index.htm;
server_name example.com www.example.com;
location / {
try_files $uri $uri/ =404;
}
location /api {
proxy_set_header X-Real-IP $remote_addr;
proxy_pass http://127.0.0.1:8888;
proxy_http_version 1.1;
}
...
}
In Aqueduct I serve the files using a FileController:
router.route("/api/v1/app/*")
.link(() => LogController(context))
.link(() => FileController("public/app/")
..setContentTypeForExtension('apk', ContentType('application', 'vnd.android.package-archive')));
However, any files that it returns don't include the Content-Length header. That means that I can't show download progress.
I tried creating a custom FileController where I added the headers in manually:
final contentLengthValue = file.lengthSync();
return Response.ok(byteStream,
headers: {HttpHeaders.lastModifiedHeader: lastModifiedDateStringValue,
HttpHeaders.contentLengthHeader: contentLengthValue,
'x-decompressed-content-length': contentLengthValue,
HttpHeaders.cacheControlHeader: 'no-transform',
HttpHeaders.acceptRangesHeader: 'bytes'})
..cachePolicy = _policyForFile(file)
..encodeBody = false
..contentType = contentType;
The Content-Length header was still removed, but the x-decompressed-content-length header remained so this is a possible workaround. It just doesn't play nicely with some Flutter plugins that look for the Content-Length header and don't have a convenient way to check other headers.
Is this an Aqueduct problem or an Nginx problem? How do I solve it?
This solution works, but it skirts around the original problem. That it, it allows you to serve files that have the Content-Length in the header, but it doesn't explain why it was getting stripped in Aqueduct. Other answers are welcome.
Rather than have Aqueduct serve files, just have Nginx serve them directly.
If you can't change your API route, you can just give it an alias in the Nginx config location block. Add this before the /api location block.
location /api/v1/app/ {
alias /home/web/my_server/public/app/;
}
Now files in the app/ folder will get served by Nginx rather than Aqueduct. Nginx includes the Content-Length header in the files it returns.

Don't serve static files if backend is offline

I have the following nginx config that handles serving my static website and redirecting requests to my REST backend:
server {
listen 80 default_server;
server_name _;
# Host static content directly
location / {
root /var/www/html;
index index.html;
try_files $uri $uri/ =404;
}
# Forward api requests to REST server
location /api {
proxy_pass http://127.0.0.1:8080;
}
}
If my REST backend goes offline the proxy module returns an HTTP status of "502 Bad Gateway" and I can redirect requests to a status page by adding the following:
# Rewrite "502 Bad Gateway" to "503 Service unavailable"
error_page 502 =503 #status_offline;
# Show offline status page whenever 503 status is returned
error_page 503 #status_offline;
location #status_offline {
root /var/www/html;
rewrite ^(.*)$ /status_offline.html break;
}
However, this will only work for requests that access the REST backend directly. How can I redirect requests to my static website in the same way whenever the backend is offline?
Nginx does have some health check and status monitoring capabilities that seem like they could be related, but I couldn't find a proper way to use them.
While its intended use case is actually for authorization, I found nginx's auth_request module to work for me:
# Host static content directly
location / {
# Check if REST server is online before serving site
auth_request /api/status; # Continues when 2xx HTTP status is returned
# If not, redirect to offline status page
error_page 500 =503 #status_offline;
root /var/www/html;
index index.html;
try_files $uri $uri/ =404;
}
It will call /api/status as a subrequest before serving the static content and will only continue when the subrequest returns an HTTP status in the 200 range. It seems to return status 500 when the server is offline.
This method might have some performance implications since you're now always doing an extra request, but that seems to be an inherent requirement of checking whether your service is online.
I think this is the correct answer - auth request is ideal for any situation where you want to "ping" a backend before returning the requested content.
I have used a similar scheme in the past for an nginx server where I wanted to check if an auth header was correct before proxying to an S3 bucket.

NGINX redirects to http

I am a newbie to NGINX and have been trying to get this problem sorted out.
Here is the NGINX configuration that works pretty well for most of the part, however when a request is placed without the trailing slash at the end, it redirects to http://$host instead of https://$host. It was forwarding with the port earlier, but I turned off port_in_redirect, which disabled showing up the port number in the browser.
Somehow https://domain.com/xyz/abc still gets redirected to http://domain.com/xyz/abc/
My guess is try_files is somehow not retaining the domain name
I am sure there is something wrong in the configuration, but I have no deep insights on whats causing it
Any inputs is highly appreciate
server {
listen 8080;
server_name _;
location /xyz/abc {
alias /var/www/html/;
try_files $uri $uri/ /xyz/abc/index.html;
}
location ~ ^/xyz/foo/(.*) {
return 301 https://$host/xyz/abc/foo/$1;
}
Below is the curl output
curl -I https://domain.com/xyz/abc
HTTP/1.1 301 Moved Permanently
Server: nginx/1.6.3
Date: Thu, 27 Jan 2016 08:18:27 GMT
Content-Type: text/html
Content-Length: 184
Location: http://domain.com/xyz/abc/
Connection: keep-alive

Nginx config for single page app with HTML5 App Cache

I'm trying to build a single page app that utilizes HTML5 App Cache, which will cache a whole new version of the app for every distinct URL, thus I must redirect everyone to / and have my app route them afterward (this is the solution used on devdocs.io).
Here's my nginx config. I want all requests to send a file if it exists, redirect to my API at /auth and /api, and redirect all other requests to index.html. Why is the following configuration causing my browser to say that there is a redirect loop? If the user hits location block #2 and his route doesn't match a static file, he's sent to location block #3, which will redirect him to "/" which should hit location block #1 and serve index.html, correct? What is causing the redirect loop here? Is there a better way to accomplish this?
root /files/whatever/public;
index index.html;
# If the location is exactly "/", send index.html.
location = / {
try_files $uri /index.html;
}
location / {
try_files $uri #redirectToIndex;
}
# Set the cookie of the initialPath and redirect to "/".
location #redirectToIndex {
add_header Set-Cookie "initialPath=$request_uri; path=/";
return 302 $scheme://$host/;
}
# Proxy requests to "/auth" and "/api" to the server.
location ~* (^\/auth)|(^\/api) {
proxy_pass http://application_upstream;
proxy_redirect off;
}
That loop message suggests that /files/whatever/public/index.html doesn't exist, so the try_files in location / doesn't find $uri when it's equal to /index.html, so the try_files always internally redirects those requests to the # location which does the external redirect.
Unless you have a more complicated setup than you've outlined, I don't think you need to do so much. You shouldn't need external redirects (or even internal redirects) or server-side cookie sending for a one-file js app. The regex match for app and api wasn't quite right, either.
root /files/whatever/public;
index index.html;
location / {
try_files $uri /index.html =404;
}
# Proxy requests to "/auth" and "/api" to the server.
location ~ ^/(auth|api) {
proxy_pass http://application_upstream;
proxy_redirect off;
}

nginx static index redirect

This seems ridiculous but I've not found a working answer in over an hour of searching.
I have a static website running off nginx (which happens to be behind Varnish). The index file is called index.html. I want to redirect anyone who actually visits the URL mydomain.com/index.html back to mydomain.com.
Here is my nginx config for the site:
server {
listen 8080;
server_name www.mydomain.com;
port_in_redirect off;
location / {
root /usr/share/nginx/www.mydomain.com/public;
index index.html;
}
rewrite /index.html http://www.mydomain.com/ permanent;
}
http://www.mydomain.com/index.html responds as expected with a 301 with the location http://www.mydomain.com/ but unfortunately http://www.mydomain.com/ also serves a 301 back to itself so we get a redirect loop.
How can I tell nginx to only serve the 301 if index.html is literally in the request?
Add a new location block to handle your homepage, and use try_files directive (instead of "index index.html;") to look for the index.html file directly. Note that try_files requires you to enter at least 2 choices. So I put the same file twice.
location = / {
root /usr/share/nginx/www.mydomain.com/public;
try_files /index.html /index.html;
}
Looks good based on my experiment:
curl -iL http://www.mydomain.com/index.html
HTTP/1.1 301 Moved Permanently
Server: nginx
Date: Sat, 16 Mar 2013 09:07:27 GMT
Content-Type: text/html
Content-Length: 178
Connection: keep-alive
Location: http://www.mydomain.com/
HTTP/1.1 200 OK
Server: nginx
Date: Sat, 16 Mar 2013 09:07:27 GMT
Content-Type: text/html
Content-Length: 4
Last-Modified: Sat, 16 Mar 2013 08:05:47 GMT
Connection: keep-alive
Accept-Ranges: bytes
[UPDATE]
The root cause of the redirect loop is the 'index' directive, which triggers nginx to do another round of location match again. That's how the rewrite rule outside the location block gets executed again, causing the loop. So the 'index' directive is like a "rewrite...last;" directive. You don't want that in your case.
The trick is to not trigger another location match again. try_files can do that efficiently. That's why I picked it in my original answer. However, if you like, another simple fix is to replace
index index.html;
by
rewrite ^/$ /index.html break;
inside your original "location /" block. This 'rewrite...break;' directive will keep nginx stay inside the same location block, effectively stop the loop. However, the side effect of this approach is that you lose the functionality provided by 'index' directive.
[UPDATE 2]
Actually, index directive executes after rewrite directive. So the following also works. Note that I just added the rewrite...break; line. If the request uri is "/", nginx finds the existing file /index.html from the rewrite rule first. So the index directive is never being triggered for this request. As a result, both directives can work together.
location / {
root /usr/share/nginx/www.mydomain.com/public;
index index.html;
rewrite ^/$ /index.html break;
}
Looks like you really don't want index.php to show up in the address bar, is that correct?
If you add a rewrite directive to the nginx config, you'll get a redirect loop, as you have experienced. If you are open to a javascript solution, you can place this anywhere in your index.html to silently rewrite the address bar:
<script>
history.pushState(null, '', '/');
</script>
For more information
Keep in mind that while most modern browsers support the history API, not all do (namely, most versions of IE).