Take the 2-minute tour ×
Server Fault is a question and answer site for professional system and network administrators. It's 100% free, no registration required.

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;
share|improve this question
    
How big is the file you're uploading? –  Joel E Salas Sep 17 '13 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 '13 at 6:54
    
I added some more information. –  Tallboy Sep 17 '13 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 '13 at 7:21
    
dont you want to use include conf/allowed-ips.conf; on server{} - level? –  that guy from over there Sep 17 '13 at 8:06

1 Answer 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).

share|improve this answer
    
Added more details –  Tallboy Sep 17 '13 at 7:34
    
When I change the ajax PUT to POST it does the same thing. –  Tallboy Sep 17 '13 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 '13 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 –  that guy from over there Sep 17 '13 at 8:04

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.