Nginx causes file upload to freeze - nginx

I've been trying to figure this out for days now.
When I attempt to upload a file to my webserver written in java, about 2.5MB of the file uploads and then it just freezes. Nginx appears to be the culprit because when I upload the file to the webserver directly to the port 1234 using my vps's direct ip instead of the domain the full file uploads perfectly fine.
I am using a program also written in java to upload the file to the webserver and I am getting the error on that:
Exception in thread "main" java.io.IOException: Premature EOF
at sun.net.www.http.ChunkedInputStream.readAheadBlocking(ChunkedInputStream.java:565)
at sun.net.www.http.ChunkedInputStream.readAhead(ChunkedInputStream.java:609)
at sun.net.www.http.ChunkedInputStream.read(ChunkedInputStream.java:696)
at java.io.FilterInputStream.read(FilterInputStream.java:133)
at sun.net.www.protocol.http.HttpURLConnection$HttpInputStream.read(HttpURLConnection.java:3456)
at sun.net.www.protocol.http.HttpURLConnection$HttpInputStream.read(HttpURLConnection.java:3449)
at java.nio.file.Files.copy(Files.java:2908)
at java.nio.file.Files.copy(Files.java:3027)
at me.hellin.Main.uploadFile(Main.java:28)
at me.hellin.Main.main(Main.java:23)
This is my nginx config for it:
server {
listen 80;
server_name *redacted*;
proxy_read_timeout 300;
proxy_connect_timeout 300;
proxy_send_timeout 300;
location / {
client_max_body_size 100M;
proxy_pass http://localhost:1234;
}
}
server {
client_max_body_size 100M;
client_body_timeout 120s;
client_body_temp_path /tmp;
}
This is what I see in nginx error.log:
2022/05/03 14:14:41 [error] 2085134#2085134: *326930 connect() to [::1]:1234 failed (101: Network is unreachable) while connecting to upstream, client: *redacted*, server: *redacted*, request: "POST / HTTP/1.1", upstream: "http://[::1]:1234/", host: "*redacted*"
Here's my code just in case I did something wrong here that somehow only affects nginx:
private static InputStream upload(File file) throws Exception {
HttpURLConnection httpURLConnection = (HttpURLConnection) new URL("*redacted*")
.openConnection();
httpURLConnection.setDoOutput(true);
httpURLConnection.setRequestProperty("content-length", String.valueOf(file.length()));
httpURLConnection.setRequestProperty("content-type", "application/java-archive");
httpURLConnection.setRequestMethod("POST");
OutputStream outputStream = httpURLConnection.getOutputStream();
Files.copy(file.toPath(), outputStream);
outputStream.close();
return httpURLConnection.getInputStream();
}

I have finally found the solution to the infuriating issue. Turns out that nginx does some weird shit and I had to change the servers code (receiving the file) to send its response only after the server had closed the output stream. I was sending a response back to the client before and Ig nginx saw that and closed the connection.

Related

ngx_http_upstream_check_module checking multiple servers

I'm trying to set up multiple cluster checks in open shift using the free ngx_http_upstream_check_module
https://github.com/alibaba/tengine/blob/master/docs/modules/ngx_http_upstream_check_module.md
http status check request comes only when you explicitly specify Host in the check_http_send section
config example
upstream FPK {
server Host1:80;
server Host2:80;
check interval=3000 rise=2 fall=5 timeout=5000 type=http;
check_http_send "GET /healthz/ready HTTP/1.1\r\nHOST:Host1\r\n\r\n";
check_http_expect_alive http_2xx http_3xx;
}
server {
listen 8090;
location /healthz/ready {
proxy_pass http://FPK;
}
location /nstatus {
check_status;
access_log off;
allow all;
}
}
In this example, the check passes, but only on 1 host, I tried to add a host through a variable, but it seems that this module does not support variables and i get error
2022/09/12 09:17:13 [error] 4123691#0: check protocol http error with peer:Host:80
How to pass multiple hosts in the check_http_send section? Thanks in advance for any help

Which directive I can run before ssl_certificate_by_lua_block to get user-agent information in openresty

I am using OpenResty to generate SSL certificates dynamically.
I am trying to find out the user-agent of request before running ssl_certificate_by_lua_block and decide If I want to continue with the request or not.
I found out that ssl_client_hello_by_lua_block directive runs before ssl_certificate_by_lua_block but if I try to execute ngx.req.get_headers()["user-agent"] inside ssl_client_hello_by_lua_block I get the following error
2022/06/13 09:20:58 [error] 31918#31918: *18 lua entry thread aborted: runtime error: ssl_client_hello_by_lua:6: API disabled in the current context
stack traceback:
coroutine 0:
[C]: in function 'error'
/usr/local/openresty/lualib/resty/core/request.lua:140: in function 'get_headers'
ssl_client_hello_by_lua:6: in main chunk, context: ssl_client_hello_by_lua*, client: 1.2.3.4, server: 0.0.0.0:443
I tried rewrite_by_lua_block but it runs after ssl_certificate_by_lua_block
Are there any directive that can let me access ngx.req.get_headers()["user-agent"] and run before ssl_certificate_by_lua_block as well?
My Nginx conf for reference.
nginx.conf
# HTTPS server
server {
listen 443 ssl;
rewrite_by_lua_block {
local user_agent = ngx.req.get_headers()["user-agent"]
ngx.log(ngx.ERR, "rewrite_by_lua_block user_agent -- > ", user_agent)
}
ssl_client_hello_by_lua_block {
ngx.log(ngx.ERR, "I am from ssl_client_hello_by_lua_block")
local ssl_clt = require "ngx.ssl.clienthello"
local host, err = ssl_clt.get_client_hello_server_name()
ngx.log(ngx.ERR, "hosts -- > ", host)
-- local user_agent = ngx.req.get_headers()["user-agent"]
-- ngx.log(ngx.ERR, "user_agent -- > ", user_agent)
}
ssl_certificate_by_lua_block {
auto_ssl:ssl_certificate()
}
ssl_certificate /etc/ssl/resty-auto-ssl-fallback.crt;
ssl_certificate_key /etc/ssl/resty-auto-ssl-fallback.key;
location / {
proxy_pass http://backend_proxy$request_uri;
}
}
If someone is facing the same issue.
Here is the email group of OpenResty that helped me.
I was not thinking correctly. The certificate negotiation happens before a client send user-agent data(that comes in after the SYNACK reaches the client). So you cant save issuing the certificate in the process. Hard luck.
Once the handshake and the Client/Server Hello happens then the server has the user-agent, you can do the blocking under access_by_lua_block.

How can I reference a variable set by lua in nginx?

I am using nginx lua docker image firesh/nginx-lua:alpine-3.4. And i tried to use environment variable in nginx.config file. Below is the configuration in /etc/nginx/nginx.conf.
user nginx;
env ES_USERNAME;
env ES_PWD;
worker_processes 1;
events {
worker_connections 10240;
}
http {
server {
listen 8080;
server_name localhost;
set_by_lua $es_username os.getenv("ES_USERNAME");
set_by_lua $es_pwd os.getenv("ES_PWD");
location /health {
proxy_pass http://$es_username:$es_pwd#elk-es-http:9200/_cluster/health;
}
...
After launching the container, I see this error in the log:
2021/11/18 01:07:14 [error] 6#6: *6 failed to load inlined Lua code: set_by_lua:1: unexpected symbol near '"http://"', client: 10.0.4.122, server: localhost, request: "GET /health HTTP/1.1", host: "10.0.2.170:8080"
The problem is that the url after proxy_pass is not reading the variable from lua. It treats the ${es_username} as a string rather than read its value. What is the correct way to use that?
That sounds strange. I rather expect both $es_username and $es_pwd variables will have an empty value. set_by_lua expects a function that should return a value, and your returns nothing. The correct usage is
set_by_lua $es_username 'return os.getenv("ES_USERNAME")';
set_by_lua $es_pwd 'return os.getenv("ES_PWD")';

NGINX read body from proxy_pass response

I have two servers:
NGINX (it exchanges file id to file path)
Golang (it accepts file id and return it's path)
Ex: When browser client makes request to https://example.com/file?id=123, NGINX should proxy this request to Golang server https://go.example.com/getpath?file_id=123, which will return the response to NGINX:
{
data: {
filePath: "/static/..."
},
status: "ok"
}
Then NGINX should get value from filePath and return file from the location.
So the question is how to read response (get filePath) in NGINX?
I assume you are software developer and your have full control over your application so there is no need to force square peg in a round hole here.
Different kinds of reverse proxies support ESI(Edge Side Includes) technology which allow developer to replace different parts of responce body with content of static files or with response bodies from upstream servers.
Nginx has such technology as well. It is called SSI (Server Side Includes).
location /file {
ssi on;
proxy_pass http://go.example.com;
}
Your upstream server can produce body with content <!--# include file="/path-to-static-files/some-static-file.ext" --> and nginx will replace this in-body directive with content of the file.
But you mentioned streaming...
It means that files will be of arbitrary sizes and building response with SSI would certainly eat precious RAM resources so we need a Plan #B.
There is "good enough" method to feed big files to the clients without showing static location of the file to the client.
You can use nginx's error handler to server static files based on information supplied by upstream server.
Upstream server for example can send back redirect 302 with Location header field containing real file path to the file.
This response does not reach the client and is feed into error handler.
Here is an example of config:
location /file {
error_page 302 = #service_static_file;
proxy_intercept_errors on;
proxy_set_header Host $host;
proxy_pass http://go.example.com;
}
location #service_static_file {
root /hidden-files;
try_files $upstream_http_location 404.html;
}
With this method you will be able to serve files without over-loading your system while having control over whom do you give the file.
For this to work your upstream server should respond with status 302 and with typical "Location:" field and nginx will use location content to find the file in the "new" root for static files.
The reason for this method to be of "good enough" type (instead of perfect) because it does not support partial requests (i.e. Range: bytes ...)
Looks like you are wanting to make an api call for data to run decision and logic against. That's not quite what proxying is about.
The core proxy ability of nginx is not designed for what you are looking to do.
Possible workaround: extending nginx...
Nginx + PHP
Your php code would do the leg work.
Serve as a client to connect to the Golang server and apply additional logic to the response.
<?php
$response = file_get_contents('https://go.example.com/getpath?file_id='.$_GET["id"]);
preg_match_all("/filePath: \"(.*?)\"/", $response, $filePath);
readfile($filePath[1][0]);
?>
location /getpath {
try_files /getpath.php;
}
This is just the pseudo-code example to get it rolling.
Some miscellaneous observations / comments:
The Golang response doesn't look like valid json, replace preg_match_all with json_decode if so.
readfile is not super efficient. Consider being creative with a 302 response.
Nginx + Lua
sites-enabled:
lua_package_path "/etc/nginx/conf.d/lib/?.lua;;";
server {
listen 80 default_server;
listen [::]:80 default_server;
location /getfile {
root /var/www/html;
resolver 8.8.8.8;
set $filepath "/index.html";
access_by_lua_file /etc/nginx/conf.d/getfile.lua;
try_files $filepath =404;
}
}
Test if lua is behaving as expected:
getfile.lua (v1)
ngx.var.filepath = "/static/...";
Simplify the Golang response body to just return a bland path then use it to set filepath:
getfile.lua (v2)
local http = require "resty.http"
local httpc = http.new()
local query_string = ngx.req.get_uri_args()
local res, err = httpc:request_uri('https://go.example.com/getpath?file_id=' .. query_string["id"], {
method = "GET",
keepalive_timeout = 60,
keepalive_pool = 10
})
if res and res.status == ngx.HTTP_OK then
body = string.gsub(res.body, '[\r\n%z]', '')
ngx.var.filepath = body;
ngx.log(ngx.ERR, "[" .. body .. "]");
else
ngx.log(ngx.ERR, "missing response");
ngx.exit(504);
end
resty.http
mkdir -p /etc/nginx/conf.d/lib/resty
wget "https://raw.githubusercontent.com/ledgetech/lua-resty-http/master/lib/resty/http_headers.lua" -P /etc/nginx/conf.d/lib/resty
wget "https://raw.githubusercontent.com/ledgetech/lua-resty-http/master/lib/resty/http.lua" -P /etc/nginx/conf.d/lib/resty

nginx upstream does not be recognized by proxy_pass

I want my nginx to pass different uri's to different backends,so I thought I do that:
server {
listen 8090;
access_log /var/log/nginx/nginx_access.log combined;
error_log /var/log/nginx/nginx_error.log debug;
location /bar {
proxy_pass http://backend2;
}
location /foo {
proxy_pass http://backend2;
}
location / {
proxy_pass http://backend1;
}
}
upstream backend1 {
server 10.33.12.41:8080;
server 127.0.0.1:8080 max_fails=3;
}
upstream backend2 {
server 10.33.12.41:8080;
server 10.33.12.43:8080;
}
If I call wget http://mynginxserver:8090/ i get the following:
wget http://mynginxserver:8090/
--2015-09-18 11:58:21-- http://mynginxserver:8090/
Connecting to mynginxserver:8090... connected.
HTTP request sent, awaiting response... 302 Found
Location: http://backend1/
[following]
--2015-09-18 11:58:21-- http://backend1/
Resolving backend1 (backend1)... failed: Temporary failure in name resolution.
wget: unable to resolve host address ‘backend1’
Why does it try to resolve backend1? I don't get it. Please help ;)
Regards,
Snooops
My Fault:
1st it should have been postet here: serverfault.com
and 2nd its already solved here:
https://serverfault.com/questions/590044/nginx-proxy-pass-config

Resources