pax_global_header00006660000000000000000000000064146475622400014524gustar00rootroot0000000000000052 comment=1ff19d44db835059b8b27528667012978215cacc golang-github-tomasen-fcgi-client-0.0~git20180423.2bb3d81/000077500000000000000000000000001464756224000225325ustar00rootroot00000000000000golang-github-tomasen-fcgi-client-0.0~git20180423.2bb3d81/.gitignore000066400000000000000000000000061464756224000245160ustar00rootroot00000000000000.idea golang-github-tomasen-fcgi-client-0.0~git20180423.2bb3d81/.travis.yml000066400000000000000000000000321464756224000246360ustar00rootroot00000000000000language: go go: - tip golang-github-tomasen-fcgi-client-0.0~git20180423.2bb3d81/LICENSE000066400000000000000000000027461464756224000235500ustar00rootroot00000000000000BSD 3-Clause License Copyright (c) 2017, SHEN SHENG All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. golang-github-tomasen-fcgi-client-0.0~git20180423.2bb3d81/README.md000066400000000000000000000112341464756224000240120ustar00rootroot00000000000000### Go fastcgi client with fcgi params support [![Build Status](https://travis-ci.org/tomasen/fcgi_client.svg?branch=master)](https://travis-ci.org/tomasen/fcgi_client) [![GoDoc](https://godoc.org/github.com/tomasen/fcgi_client?status.svg)](http://godoc.org/github.com/tomasen/fcgi_client) ### Examples simple get request ```go func main() { reqParams := "name=value" env := make(map[string]string) env["SCRIPT_FILENAME"] = "/home/www/test.php" env["SERVER_SOFTWARE"] = "go / fcgiclient " env["REMOTE_ADDR"] = "127.0.0.1" env["QUERY_STRING"] = reqParams fcgi, err := fcgiclient.Dial("unix", "/tmp/php-fpm.sock") if err != nil { log.Println("err:", err) } resp, err := fcgi.Get(env) if err != nil { log.Println("err:", err) } content, err = ioutil.ReadAll(resp.Body) if err != nil { log.Println("err:", err) } log.Println("content:", string(content)) } ``` or post form data ```go func main() { env := make(map[string]string) env["SCRIPT_FILENAME"] = "/home/www/test.php" fcgi, err := fcgiclient.Dial("unix", "/tmp/php-fpm.sock") if err != nil { log.Println("err:", err) } resp, err := fcgi.PostForm(env, url.Values{"foo": {"bar"}}) if err != nil { log.Println("err:", err) } content, err = ioutil.ReadAll(resp.Body) if err != nil { log.Println("err:", err) } log.Println("content:", string(content)) } ``` or send file ```go func main() { env := make(map[string]string) env["SCRIPT_FILENAME"] = "/home/www/test.php" fcgi, err := fcgiclient.Dial("unix", "/tmp/php-fpm.sock") if err != nil { log.Println("err:", err) } resp, err := fcgi.PostFile(env, url.Values{"foo": {"bar"}}, map[string]string{"file1":"/path/to/file1"}) if err != nil { log.Println("err:", err) } content, err = ioutil.ReadAll(resp.Body) if err != nil { log.Println("err:", err) } log.Println("content:", string(content)) } ``` More examples can be found in [fcgiclient_test.go](https://github.com/tomasen/fcgi_client/src/tip/fcgiclient_test.go) ### Functions #### func Dial func Dial(network, address string) (fcgi *FCGIClient, err error) Connects to the fcgi responder at the specified network address. See func [net.Dial](http://golang.org/pkg/net/#Dial) for a description of the network and address parameters. #### func DialTimeout func DialTimeout(network, address string, timeout time.Duration) (fcgi *FCGIClient, err error) Connects to the fcgi responder at the specified network address with timeout. See func [net.DialTimeout](http://golang.org/pkg/net/#DialTimeout) for a description of the network, address and timeout parameters. #### func (*FCGIClient) Get func (this *FCGIClient) Get(p map[string]string) (resp *http.Response, err error) Get issues a GET request to the fcgi responder. #### func (*FCGIClient) Post func (this *FCGIClient) Post(p map[string]string, bodyType string, body io.Reader, l int) (resp *http.Response, err error) Get issues a Post request to the fcgi responder. with request body in the format that bodyType specified #### func (*FCGIClient) PostFile func (this *FCGIClient) PostFile(p map[string]string, data url.Values, file map[string]string) (resp *http.Response, err error) PostFile issues a POST to the fcgi responder in multipart(RFC 2046) standard, with form as a string key to a list values (url.Values), and/or with file as a string key to a list file path. #### func (*FCGIClient) PostForm func (this *FCGIClient) PostForm(p map[string]string, data url.Values) (resp *http.Response, err error) PostForm issues a POST to the fcgi responder, with form as a string key to a list values (url.Values) #### func (*FCGIClient) Request func (this *FCGIClient) Do(p map[string]string, req io.Reader) (r io.Reader, err error) Request returns a HTTP Response with Header and Body from fcgi responder #### func (*FCGIClient) Do func (this *FCGIClient) Request(p map[string]string, req io.Reader) (resp *http.Response, err error) Do made the request and returns a io.Reader that translates the data read from fcgi responder out of fcgi packet before returning it. #### func (*FCGIClient) Close func (this *FCGIClient) Close() Close fcgi connnection golang-github-tomasen-fcgi-client-0.0~git20180423.2bb3d81/fcgi_test.php000066400000000000000000000040271464756224000252150ustar00rootroot00000000000000 $val) { $md5 = md5($val); if ($key != $md5) { $stat = "FAILED"; echo "server:err ".$md5." != ".$key."\n"; } $length += strlen($key) + strlen($val); $ret .= $key."(".strlen($key).") "; } $ret .= "] ["; foreach ($_FILES as $k0 => $val) { $error = $val["error"]; if ($error == UPLOAD_ERR_OK) { $tmp_name = $val["tmp_name"]; $name = $val["name"]; $datafile = "/tmp/test.go"; move_uploaded_file($tmp_name, $datafile); $md5 = md5_file($datafile); if ($k0 != $md5) { $stat = "FAILED"; echo "server:err ".$md5." != ".$key."\n"; } $length += strlen($k0) + filesize($datafile); unlink($datafile); $ret .= $k0."(".strlen($k0).") "; } else{ $stat = "FAILED"; echo "server:file err ".file_upload_error_message($error)."\n"; } } $ret .= "]"; echo "server:got data length " .$length."\n"; } echo "-{$stat}-POST(".count($_POST).") FILE(".count($_FILES).")\n"; function file_upload_error_message($error_code) { switch ($error_code) { case UPLOAD_ERR_INI_SIZE: return 'The uploaded file exceeds the upload_max_filesize directive in php.ini'; case UPLOAD_ERR_FORM_SIZE: return 'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form'; case UPLOAD_ERR_PARTIAL: return 'The uploaded file was only partially uploaded'; case UPLOAD_ERR_NO_FILE: return 'No file was uploaded'; case UPLOAD_ERR_NO_TMP_DIR: return 'Missing a temporary folder'; case UPLOAD_ERR_CANT_WRITE: return 'Failed to write file to disk'; case UPLOAD_ERR_EXTENSION: return 'File upload stopped by extension'; default: return 'Unknown upload error'; } } golang-github-tomasen-fcgi-client-0.0~git20180423.2bb3d81/fcgiclient.go000066400000000000000000000264101464756224000251730ustar00rootroot00000000000000// Copyright 2012 Junqing Tan and The Go Authors // Use of this source code is governed by a BSD-style // Part of source code is from Go fcgi package package fcgiclient import ( "bufio" "bytes" "encoding/binary" "errors" "fmt" "io" "io/ioutil" "mime/multipart" "net" "net/http" "net/http/httputil" "net/textproto" "net/url" "os" "path/filepath" "strconv" "strings" "sync" "time" ) const FCGI_LISTENSOCK_FILENO uint8 = 0 const FCGI_HEADER_LEN uint8 = 8 const VERSION_1 uint8 = 1 const FCGI_NULL_REQUEST_ID uint8 = 0 const FCGI_KEEP_CONN uint8 = 1 const doubleCRLF = "\r\n\r\n" const ( FCGI_BEGIN_REQUEST uint8 = iota + 1 FCGI_ABORT_REQUEST FCGI_END_REQUEST FCGI_PARAMS FCGI_STDIN FCGI_STDOUT FCGI_STDERR FCGI_DATA FCGI_GET_VALUES FCGI_GET_VALUES_RESULT FCGI_UNKNOWN_TYPE FCGI_MAXTYPE = FCGI_UNKNOWN_TYPE ) const ( FCGI_RESPONDER uint8 = iota + 1 FCGI_AUTHORIZER FCGI_FILTER ) const ( FCGI_REQUEST_COMPLETE uint8 = iota FCGI_CANT_MPX_CONN FCGI_OVERLOADED FCGI_UNKNOWN_ROLE ) const ( FCGI_MAX_CONNS string = "MAX_CONNS" FCGI_MAX_REQS string = "MAX_REQS" FCGI_MPXS_CONNS string = "MPXS_CONNS" ) const ( maxWrite = 65500 // 65530 may work, but for compatibility maxPad = 255 ) type header struct { Version uint8 Type uint8 Id uint16 ContentLength uint16 PaddingLength uint8 Reserved uint8 } // for padding so we don't have to allocate all the time // not synchronized because we don't care what the contents are var pad [maxPad]byte func (h *header) init(recType uint8, reqId uint16, contentLength int) { h.Version = 1 h.Type = recType h.Id = reqId h.ContentLength = uint16(contentLength) h.PaddingLength = uint8(-contentLength & 7) } type record struct { h header rbuf []byte } func (rec *record) read(r io.Reader) (buf []byte, err error) { if err = binary.Read(r, binary.BigEndian, &rec.h); err != nil { return } if rec.h.Version != 1 { err = errors.New("fcgi: invalid header version") return } if rec.h.Type == FCGI_END_REQUEST { err = io.EOF return } n := int(rec.h.ContentLength) + int(rec.h.PaddingLength) if len(rec.rbuf) < n { rec.rbuf = make([]byte, n) } if n, err = io.ReadFull(r, rec.rbuf[:n]); err != nil { return } buf = rec.rbuf[:int(rec.h.ContentLength)] return } type FCGIClient struct { mutex sync.Mutex rwc io.ReadWriteCloser h header buf bytes.Buffer keepAlive bool reqId uint16 } // Connects to the fcgi responder at the specified network address. // See func net.Dial for a description of the network and address parameters. func Dial(network, address string) (fcgi *FCGIClient, err error) { var conn net.Conn conn, err = net.Dial(network, address) if err != nil { return } fcgi = &FCGIClient{ rwc: conn, keepAlive: false, reqId: 1, } return } // Connects to the fcgi responder at the specified network address with timeout // See func net.DialTimeout for a description of the network, address and timeout parameters. func DialTimeout(network, address string, timeout time.Duration) (fcgi *FCGIClient, err error) { var conn net.Conn conn, err = net.DialTimeout(network, address, timeout) if err != nil { return } fcgi = &FCGIClient{ rwc: conn, keepAlive: false, reqId: 1, } return } // Close fcgi connnection func (this *FCGIClient) Close() { this.rwc.Close() } func (this *FCGIClient) writeRecord(recType uint8, content []byte) (err error) { this.mutex.Lock() defer this.mutex.Unlock() this.buf.Reset() this.h.init(recType, this.reqId, len(content)) if err := binary.Write(&this.buf, binary.BigEndian, this.h); err != nil { return err } if _, err := this.buf.Write(content); err != nil { return err } if _, err := this.buf.Write(pad[:this.h.PaddingLength]); err != nil { return err } _, err = this.rwc.Write(this.buf.Bytes()) return err } func (this *FCGIClient) writeBeginRequest(role uint16, flags uint8) error { b := [8]byte{byte(role >> 8), byte(role), flags} return this.writeRecord(FCGI_BEGIN_REQUEST, b[:]) } func (this *FCGIClient) writeEndRequest(appStatus int, protocolStatus uint8) error { b := make([]byte, 8) binary.BigEndian.PutUint32(b, uint32(appStatus)) b[4] = protocolStatus return this.writeRecord(FCGI_END_REQUEST, b) } func (this *FCGIClient) writePairs(recType uint8, pairs map[string]string) error { w := newWriter(this, recType) b := make([]byte, 8) nn := 0 for k, v := range pairs { m := 8 + len(k) + len(v) if m > maxWrite { // param data size exceed 65535 bytes" vl := maxWrite - 8 - len(k) v = v[:vl] } n := encodeSize(b, uint32(len(k))) n += encodeSize(b[n:], uint32(len(v))) m = n + len(k) + len(v) if (nn + m) > maxWrite { w.Flush() nn = 0 } nn += m if _, err := w.Write(b[:n]); err != nil { return err } if _, err := w.WriteString(k); err != nil { return err } if _, err := w.WriteString(v); err != nil { return err } } w.Close() return nil } func readSize(s []byte) (uint32, int) { if len(s) == 0 { return 0, 0 } size, n := uint32(s[0]), 1 if size&(1<<7) != 0 { if len(s) < 4 { return 0, 0 } n = 4 size = binary.BigEndian.Uint32(s) size &^= 1 << 31 } return size, n } func readString(s []byte, size uint32) string { if size > uint32(len(s)) { return "" } return string(s[:size]) } func encodeSize(b []byte, size uint32) int { if size > 127 { size |= 1 << 31 binary.BigEndian.PutUint32(b, size) return 4 } b[0] = byte(size) return 1 } // bufWriter encapsulates bufio.Writer but also closes the underlying stream when // Closed. type bufWriter struct { closer io.Closer *bufio.Writer } func (w *bufWriter) Close() error { if err := w.Writer.Flush(); err != nil { w.closer.Close() return err } return w.closer.Close() } func newWriter(c *FCGIClient, recType uint8) *bufWriter { s := &streamWriter{c: c, recType: recType} w := bufio.NewWriterSize(s, maxWrite) return &bufWriter{s, w} } // streamWriter abstracts out the separation of a stream into discrete records. // It only writes maxWrite bytes at a time. type streamWriter struct { c *FCGIClient recType uint8 } func (w *streamWriter) Write(p []byte) (int, error) { nn := 0 for len(p) > 0 { n := len(p) if n > maxWrite { n = maxWrite } if err := w.c.writeRecord(w.recType, p[:n]); err != nil { return nn, err } nn += n p = p[n:] } return nn, nil } func (w *streamWriter) Close() error { // send empty record to close the stream return w.c.writeRecord(w.recType, nil) } type streamReader struct { c *FCGIClient buf []byte } func (w *streamReader) Read(p []byte) (n int, err error) { if len(p) > 0 { if len(w.buf) == 0 { rec := &record{} w.buf, err = rec.read(w.c.rwc) if err != nil { return } } n = len(p) if n > len(w.buf) { n = len(w.buf) } copy(p, w.buf[:n]) w.buf = w.buf[n:] } return } // Do made the request and returns a io.Reader that translates the data read // from fcgi responder out of fcgi packet before returning it. func (this *FCGIClient) Do(p map[string]string, req io.Reader) (r io.Reader, err error) { err = this.writeBeginRequest(uint16(FCGI_RESPONDER), 0) if err != nil { return } err = this.writePairs(FCGI_PARAMS, p) if err != nil { return } body := newWriter(this, FCGI_STDIN) if req != nil { io.Copy(body, req) } body.Close() r = &streamReader{c: this} return } type badStringError struct { what string str string } func (e *badStringError) Error() string { return fmt.Sprintf("%s %q", e.what, e.str) } // Request returns a HTTP Response with Header and Body // from fcgi responder func (this *FCGIClient) Request(p map[string]string, req io.Reader) (resp *http.Response, err error) { r, err := this.Do(p, req) if err != nil { return } rb := bufio.NewReader(r) tp := textproto.NewReader(rb) resp = new(http.Response) // Parse the first line of the response. line, err := tp.ReadLine() if err != nil { if err == io.EOF { err = io.ErrUnexpectedEOF } return nil, err } if i := strings.IndexByte(line, ' '); i == -1 { err = &badStringError{"malformed HTTP response", line} } else { resp.Proto = line[:i] resp.Status = strings.TrimLeft(line[i+1:], " ") } statusCode := resp.Status if i := strings.IndexByte(resp.Status, ' '); i != -1 { statusCode = resp.Status[:i] } if len(statusCode) != 3 { err = &badStringError{"malformed HTTP status code", statusCode} } resp.StatusCode, err = strconv.Atoi(statusCode) if err != nil || resp.StatusCode < 0 { err = &badStringError{"malformed HTTP status code", statusCode} } var ok bool if resp.ProtoMajor, resp.ProtoMinor, ok = http.ParseHTTPVersion(resp.Proto); !ok { err = &badStringError{"malformed HTTP version", resp.Proto} } // Parse the response headers. mimeHeader, err := tp.ReadMIMEHeader() if err != nil { if err == io.EOF { err = io.ErrUnexpectedEOF } return nil, err } resp.Header = http.Header(mimeHeader) // TODO: fixTransferEncoding ? resp.TransferEncoding = resp.Header["Transfer-Encoding"] resp.ContentLength, _ = strconv.ParseInt(resp.Header.Get("Content-Length"), 10, 64) if chunked(resp.TransferEncoding) { resp.Body = ioutil.NopCloser(httputil.NewChunkedReader(rb)) } else { resp.Body = ioutil.NopCloser(rb) } return } // Get issues a GET request to the fcgi responder. func (this *FCGIClient) Get(p map[string]string) (resp *http.Response, err error) { p["REQUEST_METHOD"] = "GET" p["CONTENT_LENGTH"] = "0" return this.Request(p, nil) } // Get issues a Post request to the fcgi responder. with request body // in the format that bodyType specified func (this *FCGIClient) Post(p map[string]string, bodyType string, body io.Reader, l int) (resp *http.Response, err error) { if len(p["REQUEST_METHOD"]) == 0 || p["REQUEST_METHOD"] == "GET" { p["REQUEST_METHOD"] = "POST" } p["CONTENT_LENGTH"] = strconv.Itoa(l) if len(bodyType) > 0 { p["CONTENT_TYPE"] = bodyType } else { p["CONTENT_TYPE"] = "application/x-www-form-urlencoded" } return this.Request(p, body) } // PostForm issues a POST to the fcgi responder, with form // as a string key to a list values (url.Values) func (this *FCGIClient) PostForm(p map[string]string, data url.Values) (resp *http.Response, err error) { body := bytes.NewReader([]byte(data.Encode())) return this.Post(p, "application/x-www-form-urlencoded", body, body.Len()) } // PostFile issues a POST to the fcgi responder in multipart(RFC 2046) standard, // with form as a string key to a list values (url.Values), // and/or with file as a string key to a list file path. func (this *FCGIClient) PostFile(p map[string]string, data url.Values, file map[string]string) (resp *http.Response, err error) { buf := &bytes.Buffer{} writer := multipart.NewWriter(buf) bodyType := writer.FormDataContentType() for key, val := range data { for _, v0 := range val { err = writer.WriteField(key, v0) if err != nil { return } } } for key, val := range file { fd, e := os.Open(val) if e != nil { return nil, e } defer fd.Close() part, e := writer.CreateFormFile(key, filepath.Base(val)) if e != nil { return nil, e } _, err = io.Copy(part, fd) } err = writer.Close() if err != nil { return } return this.Post(p, bodyType, buf, buf.Len()) } // Checks whether chunked is part of the encodings stack func chunked(te []string) bool { return len(te) > 0 && te[0] == "chunked" } golang-github-tomasen-fcgi-client-0.0~git20180423.2bb3d81/fcgiclient_test.go000066400000000000000000000151331464756224000262320ustar00rootroot00000000000000package fcgiclient import ( "testing" "net" "net/http" "net/http/fcgi" "fmt" "time" "log" "io" "io/ioutil" "os" "bytes" "net/url" "crypto/md5" "math/rand" "path/filepath" "strconv" "strings" "encoding/binary" ) // test fcgi protocol includes: // Get, Post, Post in multipart/form-data, and Post with files // each key should be the md5 of the value or the file uploaded // sepicify remote fcgi responer ip:port to test with php // test failed if the remote fcgi(script) failed md5 verification // and output "FAILED" in response const ( script_file = "/tank/www/fcgic_test.php" //ip_port = "remote-php-serv:59000" ip_port = "127.0.0.1:59000" ) var ( t_ *testing.T = nil ) type FastCGIServer struct{} func (s FastCGIServer) ServeHTTP(resp http.ResponseWriter, req *http.Request) { req.ParseMultipartForm(100000000); stat := "PASSED" fmt.Fprintln(resp, "-") file_num := 0 { length := 0 for k0, v0 := range req.Form { h := md5.New() io.WriteString(h, v0[0]) md5 := fmt.Sprintf("%x", h.Sum(nil)) length += len(k0) length += len(v0[0]) // echo error when key != md5(val) if md5 != k0 { fmt.Fprintln(resp, "server:err ", md5, k0) stat = "FAILED" } } if req.MultipartForm != nil { file_num = len(req.MultipartForm.File) for kn, fns := range req.MultipartForm.File { //fmt.Fprintln(resp, "server:filekey ", kn ) length += len(kn) for _, f := range fns { fd, err := f.Open() if err != nil { log.Println("server:", err) return } h := md5.New() l0, err := io.Copy(h,fd) if err != nil { log.Println(err); return} length += int(l0) defer fd.Close() md5 := fmt.Sprintf("%x", h.Sum(nil)) //fmt.Fprintln(resp, "server:filemd5 ", md5 ) if kn != md5 { fmt.Fprintln(resp, "server:err ", md5, kn) stat = "FAILED" } //fmt.Fprintln(resp, "server:filename ", f.Filename ) } } } fmt.Fprintln(resp, "server:got data length", length) } fmt.Fprintln(resp, "-"+stat+"-POST(", len(req.Form), ")-FILE(", file_num, ")--") } func sendFcgi(reqType int, fcgi_params map[string]string, data []byte, posts map[string]string, files map[string]string) (content []byte) { fcgi, err := Dial("tcp", ip_port) if err != nil { log.Println("err:", err) return } length := 0 var resp *http.Response switch reqType { case 0: if len(data) > 0 { length = len(data) rd := bytes.NewReader(data) resp, err = fcgi.Post(fcgi_params, "", rd, rd.Len()) } else if len(posts) > 0 { values := url.Values{} for k,v := range posts { values.Set(k, v) length += len(k)+2+len(v) } resp, err = fcgi.PostForm(fcgi_params, values) } else { resp, err = fcgi.Get(fcgi_params) } default: values := url.Values{} for k,v := range posts { values.Set(k, v) length += len(k)+2+len(v) } for k,v := range files { fi, _ := os.Lstat(v) length += len(k) + int(fi.Size()) } resp, err = fcgi.PostFile(fcgi_params, values, files) } if err != nil { log.Println("err:", err) return } defer resp.Body.Close() content, err = ioutil.ReadAll(resp.Body) log.Println("c: send data length ≈", length, string(content)) fcgi.Close() time.Sleep(1 * time.Second) if bytes.Index(content, []byte("FAILED")) >= 0 { t_.Error("Server return failed message") } return } func generateRandFile(size int) (p string, m string) { p = filepath.Join(os.TempDir(), "fcgict"+strconv.Itoa(rand.Int())) // open output file fo, err := os.Create(p) if err != nil { panic(err) } // close fo on exit and check for its returned error defer func() { if err := fo.Close(); err != nil { panic(err) } }() h := md5.New() for i := 0; i < size/16; i++ { buf := make([]byte, 16) binary.PutVarint(buf, rand.Int63()) fo.Write(buf) h.Write(buf) } m = fmt.Sprintf("%x", h.Sum(nil)) return } func Test(t *testing.T) { // TODO: test chunked reader t_ = t rand.Seed( time.Now().UTC().UnixNano()) // server go func() { listener, err := net.Listen("tcp", ip_port) if err != nil { // handle error log.Println("listener creatation failed: ", err) } srv := new(FastCGIServer) fcgi.Serve(listener, srv) }() time.Sleep(1 * time.Second) // init fcgi_params := make(map[string]string) fcgi_params["REQUEST_METHOD"] = "GET" fcgi_params["SERVER_PROTOCOL"] = "HTTP/1.1" //fcgi_params["GATEWAY_INTERFACE"] = "CGI/1.1" fcgi_params["SCRIPT_FILENAME"] = script_file // simple GET log.Println("test:", "get") sendFcgi(0, fcgi_params, nil, nil, nil) // simple post data log.Println("test:", "post") sendFcgi(0, fcgi_params, []byte("c4ca4238a0b923820dcc509a6f75849b=1&7b8b965ad4bca0e41ab51de7b31363a1=n"), nil, nil) log.Println("test:", "post data (more than 60KB)") data := "" length := 0 for i := 0x00; i < 0xff; i++ { v0 := strings.Repeat(string(i), 256) h := md5.New() io.WriteString(h, v0) k0 := fmt.Sprintf("%x", h.Sum(nil)) length += len(k0) length += len(v0) data += k0+"="+url.QueryEscape(v0)+"&" } sendFcgi(0, fcgi_params, []byte(data), nil, nil) log.Println("test:", "post form (use url.Values)") p0 := make(map[string]string, 1) p0["c4ca4238a0b923820dcc509a6f75849b"] = "1" p0["7b8b965ad4bca0e41ab51de7b31363a1"] = "n" sendFcgi(1, fcgi_params, nil, p0, nil) log.Println("test:", "post forms (256 keys, more than 1MB)") p1 := make(map[string]string, 1) for i := 0x00; i < 0xff; i++ { v0 := strings.Repeat(string(i), 4096) h := md5.New() io.WriteString(h, v0) k0 := fmt.Sprintf("%x", h.Sum(nil)) p1[k0] = v0 } sendFcgi(1, fcgi_params, nil, p1, nil) log.Println("test:", "post file (1 file, 500KB)) ") f0 := make(map[string]string, 1) path0,m0 := generateRandFile(500000) f0[m0]=path0 sendFcgi(1, fcgi_params, nil, p1, f0) log.Println("test:", "post multiple files (2 files, 5M each) and forms (256 keys, more than 1MB data") path1,m1 := generateRandFile(5000000) f0[m1]=path1 sendFcgi(1, fcgi_params, nil, p1, f0) log.Println("test:", "post only files (2 files, 5M each)" ) sendFcgi(1, fcgi_params, nil, nil, f0) log.Println("test:", "post only 1 file") delete(f0, "m0") sendFcgi(1, fcgi_params, nil, nil, f0) os.Remove(path0) os.Remove(path1) }