Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support unbuffered request for uwsgi #431

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open

Support unbuffered request for uwsgi #431

wants to merge 1 commit into from

Conversation

ld9999999999
Copy link

Enables uwsgi to use the same style of request_buffering option as fastcgi and proxy upstreams. A chunk of this code was derived from the proxy module. I've tested this with go-uwsgi and an html page that uploads a large file.

@ld9999999999
Copy link
Author

golang test server:

package main

import (
    "net"
    "net/http"
    "io"
    "os"
    "fmt"
    "crypto/sha256"

    "github.com/mattn/go-uwsgi"
)

var uploaded int64

func handler(w http.ResponseWriter, req *http.Request) {
    fmt.Println("New client")
    nread := int64(0)
    if req.Method == "PUT" {
        uploaded = 0

        sha := sha256.New()
        uri := req.Header.Get("Path_info")
        query := req.Header.Get("Query_string")
        fmt.Printf("URI: %s ? %s ; content-length: %d\n", uri, query, req.ContentLength)

        fmt.Println("doing upload")
        buf := make([]byte, 8192)

        sz := req.ContentLength

        for sz > 0 {
            n, err := req.Body.Read(buf)
            if err != nil && err != io.EOF {
                fmt.Println("Error, not eof:", err)
                w.WriteHeader(400)
                w.Write([]byte("Error"))
                return
            }

            sha.Write(buf[0:n])

            fmt.Println("Read", n, "bytes")

            sz -= int64(n)
            nread += int64(n)
            uploaded += int64(n)
        }
        fmt.Println("Finished reading, total bytes received", nread)
        fmt.Printf("sha256: %x\n", sha.Sum(nil))
        w.WriteHeader(200)
        w.Write([]byte("OK"));
    } else {
        // return currently read
        w.WriteHeader(200)
        w.Write([]byte(fmt.Sprintf("%d", uploaded)));
    }

    return
}

func main() {
    s := "/var/tmp/uwsgitest.sock"
    os.Remove(s)
    l, e := net.Listen("unix", s)
    if e != nil {
        fmt.Println("!!! listen failed", e)
        return
    }
    os.Chmod(s, 0666)

    http.Serve(&uwsgi.Listener{l}, http.HandlerFunc(handler))
}

test client html page

<!DOCTYPE html>
<html>
<head>
<script>

function serverstat() {
    var stat = document.getElementById('serverstat');
    var req = new XMLHttpRequest();

    req.onreadystatechange = function() {
        if (req.readyState == 4) { // fully received
            if (req.status == 200) {
                stat.innerHTML = req.responseText;
            } else { // error
                console.log("GET ERROR:", req.status, req.statusText, req.responseText);
            }
        }
    }

    req.open("GET", "/uwsgitest/stat", true);
    req.send(null);
}

function doupload() {
    var freader = new FileReader();
    var xhr = new XMLHttpRequest();

    var progspan = document.getElementById('progress');
    var file = document.getElementById('filex').files[0];

    xhr.upload.addEventListener("progress", function(e) {
        if (e.lengthComputable) {
            progspan.innerHTML = e.loaded + ', ' + (e.loaded * 100) / e.total + '%';
        }
        serverstat();
    }, false);

    xhr.upload.addEventListener("load", function(e) {
        console.log("load", e);
    }, false);

    xhr.upload.addEventListener("error", function(e) {
        console.log("error", e);
    }, false);

    xhr.upload.addEventListener("abort", function(e) {
        console.log("abort", e);
    }, false);

    xhr.open("PUT", "/uwsgitest/uploadfile");

    xhr.overrideMimeType("text/plain");

    xhr.send(file);
}
</script>
</head>
<body>
<input id="filex" type="file" /><button onclick="doupload()">Upload</button><br><br>
progress: <span id="progress"></span> | server-stat <span id="serverstat"></span>
</body>
</html>

@yaoweibin
Copy link
Member

Cool, Thank your for the patch.



static ngx_int_t
ngx_http_uwsgi_output_filter(ngx_http_upstream_output_filter_ctx_t *ctx,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this work? uwsgi is not http protocol, you should translate the protocol from http to uwsgi.

@ld9999999999
Copy link
Author

It works. uwsgi is a simple protocol, especially for http connections.

tengine/nginx already does the translation from http to uwsgi before it forwards the request through.

I ran the upstream server to print out the uwsgi headers received and they appear ok, e.g:

 >  QUERY_STRING -> []
 >  REQUEST_METHOD -> [GET]
 >  CONTENT_TYPE -> []
 >  CONTENT_LENGTH -> []
 >  REQUEST_URI -> [/uwsgitest/stat]
 >  PATH_INFO -> [/uwsgitest/stat]
 >  DOCUMENT_ROOT -> [/opt/tengine/html]
 >  SERVER_PROTOCOL -> [HTTP/1.0]
 >  REMOTE_ADDR -> [192.168.1.180]
 >  REMOTE_PORT -> [29756]
 >  SERVER_PORT -> [80]
 >  SERVER_NAME -> [textbox.org,]
 >  HTTP_HOST -> [localhost]
 >  HTTP_CONNECTION -> [keep-alive]
 >  HTTP_USER_AGENT -> [Mozilla/5.0 (X11; Linux x86_64)...]
 >  HTTP_ACCEPT -> [*/*]
 >  HTTP_REFERER -> [http://localhost/uwsgitest.html]
 >  HTTP_ACCEPT_ENCODING -> [gzip,deflate,sdch]
 >  HTTP_ACCEPT_LANGUAGE -> [en-US,en;q=0.8,et;q=0.6]

In terms of what nginx sends to the upstream server, it first sends the uwsgi header [0, length-of-uwsgi-key-value-pairs, 0]:

struct {
    uint8  modifier1;
    uint16 datasize;
    uint8  modifier2;
}

Then it sends the HTTP headers as key-value pairs as an array of:

struct {
    uint16 key_size;
    uint8  key[key_size];   /* e.g. CONTENT_TYPE */
    uint16 val_size;
    uint8  val[val_size];
}

Immediately after, nginx forwards the request body as-is, i.e. there is no framing around the request body so it's the responsibility of the backend uwsgi server to process the body up to Content-length.

Also, it appears at the moment nginx doesn't support uwsgi keepalive so every request is closed immediately after being processed by the backend.

@CLAassistant
Copy link

CLAassistant commented Apr 13, 2020

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.


Leon seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account.
You have signed the CLA already but the status is still pending? Let us recheck it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants