2

I'm really not sure where else to look for the answer. Every single guide or question online says to check client_max_body_size and that solves it.

For me however, I'm trying to do a large PUT request to a PHP file that's on my own server.

I have... client_max_body_size 32M; in nginx.conf server block. I know it works because wordpress properly has the upload size set to 32MB.

Aside from that, I found that I need to possibly check 3 PHP variables, I don't recall exactly what they were but they are all large enough by far (in the hundreds of MB).

Maybe I need to do something with buffers? I have no idea :(

The script I'm using is a proxy script to communicate to 3rd party service. It logs all it's data to output, and this is the log so far.. not sure if this could allude to a problem.

I think this below is what the server is trying to send (because it's in the log file and it's writing to STDOUT, so I assume the php proxy script is trying to write a log file of the headers it's wanting to send).

When I use chrome dev tools though, I don't see Expect: 100-continue. Could this have anything to do with it?

Edit: After viewing the PHP source, it seems it was a 'bug' having 100-continue and it was messing up the proxy script... So ignore all the output below. I suppose I'll put it anyway just in case it has any other useful bits.

"PUT /rest/cart HTTP/1.1"                                                                                                             
"HOST: staging.site.com"                                                                                                              
"CONNECTION: keep-alive"                                                                                                              
"CONTENT-LENGTH: 2695"                                                                                                                
"ACCEPT: application/json, text/javascript, */*; q=0.01"                                                                              
"CACHE-CONTROL: no-cache"                                                                                                             
"ORIGIN: http://staging.site.com"                                                                                                     
"X-REQUESTED-WITH: XMLHttpRequest"                                                                                                    
"USER-AGENT: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.37 Safari/537.36    " 
"CONTENT-TYPE: application/json; charset=UTF-8    "                                                                                       
"REFERER: http://staging.site.com/order/    "                                                                                             
"ACCEPT-ENCODING: gzip,deflate,sdch    "                                                                                                  
"ACCEPT-LANGUAGE: en-US,en;q=0.8    "                                                                                                     
"COOKIE: XXX"                                                                                                                           
"Content-Type: application/json; charset=UTF-8    "                                                                                       
"Content-Length: 2695    "                                                                                                                
"X-UC-Forwarded-For: XXX    "                                                                                                  
"Expect: 100-continue    "     

Not sure if this helps.. but here is the proxy script.

<?php
// Version 0.7.  08/15/2013
//   Some of the PUT/POST requests were returning back 100 Continues.  That was wrecking havoc with the parser below causing aborted calls.
// Version 0.6.  06/22/2013
//   Headers weren't being handled correctly.  Server http status wasn't being passed along.
//   Headers with multiple values weren't iterated correctly and were being mangled (think multiple 'Set-Cookie')
// Version 0.5.  02/07/2013  Initial Version.

function http_parse_headers($header)
{
    $retVal = array();
    $fields = explode("\r\n", preg_replace('/\x0D\x0A[\x09\x20]+/', ' ', $header));
    foreach ($fields as $field) {
        if (preg_match('/([^:]+): (.+)/m', $field, $match)) {
            $match[1] = preg_replace('/(?<=^|[\x09\x20\x2D])./e', 'strtoupper("\0")', strtolower(trim($match[1])));
            if (isset($retVal[$match[1]])) {
                $retVal[$match[1]] = array($retVal[$match[1]], $match[2]);
            } else {
                $retVal[$match[1]] = trim($match[2]);
            }
        }
    }
    return $retVal;
}

function gzdecode($data)
{
    $g = tempnam('/tmp', 'ff');
    @file_put_contents($g, $data);
    ob_start();
    readgzfile($g);
    $d = ob_get_clean();
    unlink($g);
    return $d;
}

if (isset($_GET["_url"])) {
    $path = $_GET["_url"]; // get the url parameter
} else {
    die("UltraCart rest proxy script called incorrectly.  _url query parameter is required.");
}


$path = preg_replace('#[^a-z0-9/]#i', '', $path); // strip off any junk
$path = preg_replace('#/+#', '/', $path); // remove duplicate slashes if any
if (strncmp($path, '/', 1) != 0) { // if the path doesn't start with a slash, add one.
    $path = '/' . $path;
}

$additional_parameters = '';
foreach ($_GET as $k => $v) {
    if ($k != '_url') {
        if ($additional_parameters) {
            $additional_parameters = $additional_parameters . '&' . $k . "=" . urlencode($v);
        } else {
            $additional_parameters = $additional_parameters . '?' . $k . "=" . urlencode($v);
        }
    }
}

// the above filtering should remove any malicious attempts, but no worries, UltraCart has some insane firewalls to boot.
$server_get_url = "https://secure.ultracart.com" . $path . $additional_parameters;
$post_data = file_get_contents('php://input');

foreach ($_SERVER as $i => $val) {
    if (strpos($i, 'HTTP_') === 0) {
        if ($i == 'HTTP_X_UC_MERCHANT_ID') {
            $header[] = "X-UC-Merchant-Id: $val";
        } else if ($i == 'HTTP_X_UC_SHOPPING_CART_ID') {
            $header[] = "X-UC-Shopping-Cart-Id: $val";
        } else {
            $name = str_replace(array('HTTP_', '_'), array('', '-'), $i);
            $header[] = "$name: $val";
        }
    }
}

if (isset($_SERVER['CONTENT_TYPE'])) {
    $content_type = $_SERVER['CONTENT_TYPE'];
} else {
    $content_type = 'application/json';
}

$header[] = "Content-Type: " . $content_type;
$header[] = "Content-Length: " . strlen($post_data);
$header[] = "X-UC-Forwarded-For: " . $_SERVER['REMOTE_ADDR'];

$ch = curl_init($server_get_url);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $_SERVER['REQUEST_METHOD']);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_TIMEOUT, 100);
curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($ch, CURLOPT_VERBOSE, 1);
curl_setopt($ch, CURLOPT_HEADER, 1);
curl_setopt($ch, CURLOPT_ENCODING, 1);

if (strlen($post_data) > 0) {
    curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
}

$response = curl_exec($ch);
//error_log("start response ===========================================");
//error_log("start raw response ===============");
//error_log($response);
//error_log("end raw response ===============");


// grab the status code and set the proxy request result to that.
$first_line = '';
$beginning_of_real_http_status = 0; // a index marker for the second http status if the server returns 100 Continue (PUTS/POSTS)
if (strlen($response) > 0) {
    $first_line = substr($response, 0, strpos($response, "\n") - 1);
    $first_line = trim($first_line);

    // Is the first line an HTTP/1.1 100 Continue?
    // If so, search for the next empty line and begin there.
    preg_match("/100\s+Continue/i", $first_line, $output_array);
    if (count($output_array) > 0) {
        // we have an HTTP Continue.  Skip down to the next status code.

        if (preg_match('#^\s*$#m', $response, $matches, PREG_OFFSET_CAPTURE)) {
            $beginning_of_real_http_status = $matches[0][1] + 2;
        }

        $real_headers = explode("\n", substr($response, $beginning_of_real_http_status));
        $first_line = $real_headers[0];
//        $first_line = substr($response, $beginning_of_real_http_status, strpos($response, "\n", $beginning_of_real_http_status) - 1);
        $first_line = trim($first_line);
    }

    //error_log('$first_line:[' . $first_line . ']');
    header($first_line);
}


//error_log('$beginning_of_real_http_status:' . $beginning_of_real_http_status);

if (curl_errno($ch)) {
    print curl_error($ch);
} else {
    $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
    $header = substr($response, $beginning_of_real_http_status, $header_size - $beginning_of_real_http_status);
    $response_headers = http_parse_headers($header);
    foreach ($response_headers as $header_key => $header_value) {
        if ($header_key != 'Content-Encoding' && $header_key != 'Vary' && $header_key != 'Connection' && $header_key != 'Transfer-Encoding') {
            if ($header_key == 'Content-Length' && $header_value == "0") {
                /* ignore this, it's from an HTTP 1.1 100 Continue and will destroy the result if passed along. */
            } else {
                if (is_array($header_value)) {
                    foreach ($header_value as $val) {
                        //error_log("$header_key: $val");
                        header("$header_key: $val", false);
                    }
                } else {
                    //error_log("$header_key: $header_value");
                    header("$header_key: $header_value", false);
                }

            }
        }
    }
    $body = substr($response, $header_size);
    echo $body;
    curl_close($ch);
}
//error_log("end response ===========================================");
?>

nginx.conf

user  www www;
worker_processes  4;
error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;

worker_rlimit_nofile  200000;

events {
    worker_connections  32768;
    use                 epoll;
    multi_accept        on;
}

http {

    #auth_basic "Restricted";
    #auth_basic_user_file /home/www/.htpasswd;

    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    access_log  off;

    log_format  main '$remote_addr - $remote_user [$time_local] "$request" '
                     '$status $body_bytes_sent "$http_referer" '
                     '"$http_user_agent" "$http_x_forwarded_for"';

    ssl_prefer_server_ciphers  on;
    ssl_session_timeout        10m;
    ssl_session_cache          builtin:1000 shared:SSL:20m;
    ssl_protocols              SSLv3 TLSv1;
    ssl_ciphers                ECDHE-RSA-AES128-SHA256:AES128-GCM-SHA256:RC4:HIGH:!MD5:!aNULL:!EDH;

    sendfile       on;
    tcp_nopush     on;
    tcp_nodelay    on; 
    server_tokens  off;

    gzip               on;
    gzip_http_version  1.0;
    gzip_disable       "msie6";
    gzip_comp_level    1;
    gzip_buffers       16 8k;
    gzip_min_length    256;
    gzip_proxied       any;
    gzip_vary          on;
    gzip_types
      # text/html is always compressed by HttpGzipModule
      text/css
      text/plain
      text/x-component
      application/javascript
      application/json
      application/xml
      application/xhtml+xml
      application/x-font-ttf
      application/x-font-opentype
      application/vnd.ms-fontobject
      image/svg+xml
      image/x-icon;

    include sites-enabled/*;
}

staging.site.conf

server {

    listen       80 deferred;
    server_name  staging.site.com;
    root         /var/www/html/site.com;

    index   index.html index.htm index.php;
    charset utf-8;

    access_log /var/log/nginx/site.com-access_log;

    include conf/wordpress.conf;
    include conf/bwp-security.conf;
    include conf/base.conf;
    include conf/php.conf;    

    location / {
      include conf/allowed-ips.conf;    
      try_files $uri $uri/ /index.php?$args;
    }
}

php.conf

location ~ \.php$ {
    try_files $uri =404;
    fastcgi_split_path_info ^(.+\.php)(/.+)$;
    include         fastcgi_params;
    fastcgi_index   index.php;
    fastcgi_param   SCRIPT_FILENAME  $document_root$fastcgi_script_name;
    fastcgi_buffers 32 32k;
    fastcgi_buffer_size 32k;
    fastcgi_intercept_errors off;
    fastcgi_pass   unix:/tmp/php5-fpm.sock;
}

wordpress.conf

client_max_body_size 32M;
5
  • How big is the file you're uploading? Sep 17, 2013 at 6:53
  • I'm not uploading a file.. its only a few KB of data via a PUT request. < 100kb. Ironically enough I can upload huge files... so it's got to be something else. I don't know how to debug it though, as all the error logs are not helpful from what I can tell.
    – Tallboy
    Sep 17, 2013 at 6:54
  • I added some more information.
    – Tallboy
    Sep 17, 2013 at 7:07
  • I'm also using nginx 1.2.9, as it appears this was a bug (related to 100-continue and 413 in 1.2.6
    – Tallboy
    Sep 17, 2013 at 7:21
  • dont you want to use include conf/allowed-ips.conf; on server{} - level? Sep 17, 2013 at 8:06

1 Answer 1

1

can you please append your related nginx-conf?

do you know if the request makes it over to the php-locations or is it stopped @ nginx?

So you can upload via POST, but not via PUT? I'd start investigate on that issue; What happens when your change your PUT to POST?

when googling PUT 413: Request Entity Too Large nginx the most referenced advise is what you did already, and i wouldnt start messing with client_*_temp - vars and temp_caching (atm).

4
  • Added more details
    – Tallboy
    Sep 17, 2013 at 7:34
  • When I change the ajax PUT to POST it does the same thing.
    – Tallboy
    Sep 17, 2013 at 7:35
  • Good point about the PHP thing.. i commented out all the code in the proxy script and it PUT's just fine... I guess this is now a PHP question, although I wonder what could be in this file that is falsely triggering 413?
    – Tallboy
    Sep 17, 2013 at 7:49
  • could be that nginx 'misunderstood' what php is telling back, dunno much about php, but there are max_body - dirctives too in php.ini OR, if you use it, suphp/suhosin etc Sep 17, 2013 at 8:04

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .