Mercurial Hosting > d2o
comparison fcgi/fcgi.go @ 3:eb705d4cdcd7
fix fcgi
| author | Atarwn Gard <a@qwa.su> |
|---|---|
| date | Mon, 09 Mar 2026 02:16:06 +0500 |
| parents | 48bdab3eec8a |
| children |
comparison
equal
deleted
inserted
replaced
| 2:d19133be91ba | 3:eb705d4cdcd7 |
|---|---|
| 1 // Package fcgi implements a minimal FastCGI client. | 1 // Copyright 2012 Junqing Tan <ivan@mysqlab.net> and The Go Authors |
| 2 // | 2 // Use of this source code is governed by a BSD-style |
| 3 // Supports sending a single request over a pre-dialed net.Conn and streaming | 3 // Part of source code is from Go fcgi package |
| 4 // the CGI response back to an http.ResponseWriter. | 4 |
| 5 package fcgi | 5 package fcgi |
| 6 | 6 |
| 7 import ( | 7 import ( |
| 8 "bufio" | 8 "bufio" |
| 9 "bytes" | |
| 9 "encoding/binary" | 10 "encoding/binary" |
| 11 "errors" | |
| 12 "fmt" | |
| 10 "io" | 13 "io" |
| 14 "io/ioutil" | |
| 15 "mime/multipart" | |
| 16 "net" | |
| 11 "net/http" | 17 "net/http" |
| 18 "net/http/httputil" | |
| 12 "net/textproto" | 19 "net/textproto" |
| 20 "net/url" | |
| 21 "os" | |
| 22 "path/filepath" | |
| 23 "strconv" | |
| 13 "strings" | 24 "strings" |
| 25 "sync" | |
| 26 "time" | |
| 14 ) | 27 ) |
| 15 | 28 |
| 16 // Reference: https://fastcgi-archives.github.io/FastCGI_Specification.html | 29 const FCGI_LISTENSOCK_FILENO uint8 = 0 |
| 30 const FCGI_HEADER_LEN uint8 = 8 | |
| 31 const VERSION_1 uint8 = 1 | |
| 32 const FCGI_NULL_REQUEST_ID uint8 = 0 | |
| 33 const FCGI_KEEP_CONN uint8 = 1 | |
| 34 const doubleCRLF = "\r\n\r\n" | |
| 17 | 35 |
| 18 const ( | 36 const ( |
| 19 fcgiVersion = 1 | 37 FCGI_BEGIN_REQUEST uint8 = iota + 1 |
| 20 fcgiBeginRequest = 1 | 38 FCGI_ABORT_REQUEST |
| 21 fcgiParams = 4 | 39 FCGI_END_REQUEST |
| 22 fcgiStdin = 5 | 40 FCGI_PARAMS |
| 23 fcgiStdout = 6 | 41 FCGI_STDIN |
| 24 fcgiEndRequest = 3 | 42 FCGI_STDOUT |
| 25 fcgiRoleResponder = 1 | 43 FCGI_STDERR |
| 26 fcgiRequestID = 1 | 44 FCGI_DATA |
| 45 FCGI_GET_VALUES | |
| 46 FCGI_GET_VALUES_RESULT | |
| 47 FCGI_UNKNOWN_TYPE | |
| 48 FCGI_MAXTYPE = FCGI_UNKNOWN_TYPE | |
| 27 ) | 49 ) |
| 28 | 50 |
| 29 // Do sends params and the request body over conn as a FastCGI request, then | 51 const ( |
| 30 // parses the response and writes it to w. conn is closed by the caller. | 52 FCGI_RESPONDER uint8 = iota + 1 |
| 31 func Do(w http.ResponseWriter, r *http.Request, conn io.ReadWriter, params map[string]string) error { | 53 FCGI_AUTHORIZER |
| 32 bw := bufio.NewWriter(conn) | 54 FCGI_FILTER |
| 33 | 55 ) |
| 34 if err := writeRecord(bw, fcgiBeginRequest, fcgiRequestID, | 56 |
| 35 []byte{0, fcgiRoleResponder, 0, 0, 0, 0, 0, 0}); err != nil { | 57 const ( |
| 58 FCGI_REQUEST_COMPLETE uint8 = iota | |
| 59 FCGI_CANT_MPX_CONN | |
| 60 FCGI_OVERLOADED | |
| 61 FCGI_UNKNOWN_ROLE | |
| 62 ) | |
| 63 | |
| 64 const ( | |
| 65 FCGI_MAX_CONNS string = "MAX_CONNS" | |
| 66 FCGI_MAX_REQS string = "MAX_REQS" | |
| 67 FCGI_MPXS_CONNS string = "MPXS_CONNS" | |
| 68 ) | |
| 69 | |
| 70 const ( | |
| 71 maxWrite = 65500 // 65530 may work, but for compatibility | |
| 72 maxPad = 255 | |
| 73 ) | |
| 74 | |
| 75 type header struct { | |
| 76 Version uint8 | |
| 77 Type uint8 | |
| 78 Id uint16 | |
| 79 ContentLength uint16 | |
| 80 PaddingLength uint8 | |
| 81 Reserved uint8 | |
| 82 } | |
| 83 | |
| 84 // for padding so we don't have to allocate all the time | |
| 85 // not synchronized because we don't care what the contents are | |
| 86 var pad [maxPad]byte | |
| 87 | |
| 88 func (h *header) init(recType uint8, reqId uint16, contentLength int) { | |
| 89 h.Version = 1 | |
| 90 h.Type = recType | |
| 91 h.Id = reqId | |
| 92 h.ContentLength = uint16(contentLength) | |
| 93 h.PaddingLength = uint8(-contentLength & 7) | |
| 94 } | |
| 95 | |
| 96 type record struct { | |
| 97 h header | |
| 98 rbuf []byte | |
| 99 } | |
| 100 | |
| 101 func (rec *record) read(r io.Reader) (buf []byte, err error) { | |
| 102 if err = binary.Read(r, binary.BigEndian, &rec.h); err != nil { | |
| 103 return | |
| 104 } | |
| 105 if rec.h.Version != 1 { | |
| 106 err = errors.New("fcgi: invalid header version") | |
| 107 return | |
| 108 } | |
| 109 if rec.h.Type == FCGI_END_REQUEST { | |
| 110 err = io.EOF | |
| 111 return | |
| 112 } | |
| 113 n := int(rec.h.ContentLength) + int(rec.h.PaddingLength) | |
| 114 if len(rec.rbuf) < n { | |
| 115 rec.rbuf = make([]byte, n) | |
| 116 } | |
| 117 if n, err = io.ReadFull(r, rec.rbuf[:n]); err != nil { | |
| 118 return | |
| 119 } | |
| 120 buf = rec.rbuf[:int(rec.h.ContentLength)] | |
| 121 | |
| 122 return | |
| 123 } | |
| 124 | |
| 125 type FCGIClient struct { | |
| 126 mutex sync.Mutex | |
| 127 rwc io.ReadWriteCloser | |
| 128 h header | |
| 129 buf bytes.Buffer | |
| 130 keepAlive bool | |
| 131 reqId uint16 | |
| 132 } | |
| 133 | |
| 134 // Connects to the fcgi responder at the specified network address. | |
| 135 // See func net.Dial for a description of the network and address parameters. | |
| 136 func Dial(network, address string) (fcgi *FCGIClient, err error) { | |
| 137 var conn net.Conn | |
| 138 | |
| 139 conn, err = net.Dial(network, address) | |
| 140 if err != nil { | |
| 141 return | |
| 142 } | |
| 143 | |
| 144 fcgi = &FCGIClient{ | |
| 145 rwc: conn, | |
| 146 keepAlive: false, | |
| 147 reqId: 1, | |
| 148 } | |
| 149 | |
| 150 return | |
| 151 } | |
| 152 | |
| 153 // Connects to the fcgi responder at the specified network address with timeout | |
| 154 // See func net.DialTimeout for a description of the network, address and timeout parameters. | |
| 155 func DialTimeout(network, address string, timeout time.Duration) (fcgi *FCGIClient, err error) { | |
| 156 | |
| 157 var conn net.Conn | |
| 158 | |
| 159 conn, err = net.DialTimeout(network, address, timeout) | |
| 160 if err != nil { | |
| 161 return | |
| 162 } | |
| 163 | |
| 164 fcgi = &FCGIClient{ | |
| 165 rwc: conn, | |
| 166 keepAlive: false, | |
| 167 reqId: 1, | |
| 168 } | |
| 169 | |
| 170 return | |
| 171 } | |
| 172 | |
| 173 // Close fcgi connnection | |
| 174 func (this *FCGIClient) Close() { | |
| 175 this.rwc.Close() | |
| 176 } | |
| 177 | |
| 178 func (this *FCGIClient) writeRecord(recType uint8, content []byte) (err error) { | |
| 179 this.mutex.Lock() | |
| 180 defer this.mutex.Unlock() | |
| 181 this.buf.Reset() | |
| 182 this.h.init(recType, this.reqId, len(content)) | |
| 183 if err := binary.Write(&this.buf, binary.BigEndian, this.h); err != nil { | |
| 36 return err | 184 return err |
| 37 } | 185 } |
| 38 | 186 if _, err := this.buf.Write(content); err != nil { |
| 39 if err := writeRecord(bw, fcgiParams, fcgiRequestID, encodeParams(params)); err != nil { | |
| 40 return err | 187 return err |
| 41 } | 188 } |
| 42 if err := writeRecord(bw, fcgiParams, fcgiRequestID, nil); err != nil { | 189 if _, err := this.buf.Write(pad[:this.h.PaddingLength]); err != nil { |
| 43 return err | 190 return err |
| 44 } | 191 } |
| 45 | 192 _, err = this.rwc.Write(this.buf.Bytes()) |
| 46 if r.Body != nil { | 193 return err |
| 47 buf := make([]byte, 4096) | 194 } |
| 48 for { | 195 |
| 49 n, err := r.Body.Read(buf) | 196 func (this *FCGIClient) writeBeginRequest(role uint16, flags uint8) error { |
| 50 if n > 0 { | 197 b := [8]byte{byte(role >> 8), byte(role), flags} |
| 51 if werr := writeRecord(bw, fcgiStdin, fcgiRequestID, buf[:n]); werr != nil { | 198 return this.writeRecord(FCGI_BEGIN_REQUEST, b[:]) |
| 52 return werr | 199 } |
| 53 } | 200 |
| 201 func (this *FCGIClient) writeEndRequest(appStatus int, protocolStatus uint8) error { | |
| 202 b := make([]byte, 8) | |
| 203 binary.BigEndian.PutUint32(b, uint32(appStatus)) | |
| 204 b[4] = protocolStatus | |
| 205 return this.writeRecord(FCGI_END_REQUEST, b) | |
| 206 } | |
| 207 | |
| 208 func (this *FCGIClient) writePairs(recType uint8, pairs map[string]string) error { | |
| 209 w := newWriter(this, recType) | |
| 210 b := make([]byte, 8) | |
| 211 nn := 0 | |
| 212 for k, v := range pairs { | |
| 213 m := 8 + len(k) + len(v) | |
| 214 if m > maxWrite { | |
| 215 // param data size exceed 65535 bytes" | |
| 216 vl := maxWrite - 8 - len(k) | |
| 217 v = v[:vl] | |
| 218 } | |
| 219 n := encodeSize(b, uint32(len(k))) | |
| 220 n += encodeSize(b[n:], uint32(len(v))) | |
| 221 m = n + len(k) + len(v) | |
| 222 if (nn + m) > maxWrite { | |
| 223 w.Flush() | |
| 224 nn = 0 | |
| 225 } | |
| 226 nn += m | |
| 227 if _, err := w.Write(b[:n]); err != nil { | |
| 228 return err | |
| 229 } | |
| 230 if _, err := w.WriteString(k); err != nil { | |
| 231 return err | |
| 232 } | |
| 233 if _, err := w.WriteString(v); err != nil { | |
| 234 return err | |
| 235 } | |
| 236 } | |
| 237 w.Close() | |
| 238 return nil | |
| 239 } | |
| 240 | |
| 241 func readSize(s []byte) (uint32, int) { | |
| 242 if len(s) == 0 { | |
| 243 return 0, 0 | |
| 244 } | |
| 245 size, n := uint32(s[0]), 1 | |
| 246 if size&(1<<7) != 0 { | |
| 247 if len(s) < 4 { | |
| 248 return 0, 0 | |
| 249 } | |
| 250 n = 4 | |
| 251 size = binary.BigEndian.Uint32(s) | |
| 252 size &^= 1 << 31 | |
| 253 } | |
| 254 return size, n | |
| 255 } | |
| 256 | |
| 257 func readString(s []byte, size uint32) string { | |
| 258 if size > uint32(len(s)) { | |
| 259 return "" | |
| 260 } | |
| 261 return string(s[:size]) | |
| 262 } | |
| 263 | |
| 264 func encodeSize(b []byte, size uint32) int { | |
| 265 if size > 127 { | |
| 266 size |= 1 << 31 | |
| 267 binary.BigEndian.PutUint32(b, size) | |
| 268 return 4 | |
| 269 } | |
| 270 b[0] = byte(size) | |
| 271 return 1 | |
| 272 } | |
| 273 | |
| 274 // bufWriter encapsulates bufio.Writer but also closes the underlying stream when | |
| 275 // Closed. | |
| 276 type bufWriter struct { | |
| 277 closer io.Closer | |
| 278 *bufio.Writer | |
| 279 } | |
| 280 | |
| 281 func (w *bufWriter) Close() error { | |
| 282 if err := w.Writer.Flush(); err != nil { | |
| 283 w.closer.Close() | |
| 284 return err | |
| 285 } | |
| 286 return w.closer.Close() | |
| 287 } | |
| 288 | |
| 289 func newWriter(c *FCGIClient, recType uint8) *bufWriter { | |
| 290 s := &streamWriter{c: c, recType: recType} | |
| 291 w := bufio.NewWriterSize(s, maxWrite) | |
| 292 return &bufWriter{s, w} | |
| 293 } | |
| 294 | |
| 295 // streamWriter abstracts out the separation of a stream into discrete records. | |
| 296 // It only writes maxWrite bytes at a time. | |
| 297 type streamWriter struct { | |
| 298 c *FCGIClient | |
| 299 recType uint8 | |
| 300 } | |
| 301 | |
| 302 func (w *streamWriter) Write(p []byte) (int, error) { | |
| 303 nn := 0 | |
| 304 for len(p) > 0 { | |
| 305 n := len(p) | |
| 306 if n > maxWrite { | |
| 307 n = maxWrite | |
| 308 } | |
| 309 if err := w.c.writeRecord(w.recType, p[:n]); err != nil { | |
| 310 return nn, err | |
| 311 } | |
| 312 nn += n | |
| 313 p = p[n:] | |
| 314 } | |
| 315 return nn, nil | |
| 316 } | |
| 317 | |
| 318 func (w *streamWriter) Close() error { | |
| 319 // send empty record to close the stream | |
| 320 return w.c.writeRecord(w.recType, nil) | |
| 321 } | |
| 322 | |
| 323 type streamReader struct { | |
| 324 c *FCGIClient | |
| 325 buf []byte | |
| 326 } | |
| 327 | |
| 328 func (w *streamReader) Read(p []byte) (n int, err error) { | |
| 329 | |
| 330 if len(p) > 0 { | |
| 331 if len(w.buf) == 0 { | |
| 332 rec := &record{} | |
| 333 w.buf, err = rec.read(w.c.rwc) | |
| 334 if err != nil { | |
| 335 return | |
| 54 } | 336 } |
| 55 if err == io.EOF { | 337 } |
| 56 break | 338 |
| 339 n = len(p) | |
| 340 if n > len(w.buf) { | |
| 341 n = len(w.buf) | |
| 342 } | |
| 343 copy(p, w.buf[:n]) | |
| 344 w.buf = w.buf[n:] | |
| 345 } | |
| 346 | |
| 347 return | |
| 348 } | |
| 349 | |
| 350 // Do made the request and returns a io.Reader that translates the data read | |
| 351 // from fcgi responder out of fcgi packet before returning it. | |
| 352 func (this *FCGIClient) Do(p map[string]string, req io.Reader) (r io.Reader, err error) { | |
| 353 err = this.writeBeginRequest(uint16(FCGI_RESPONDER), 0) | |
| 354 if err != nil { | |
| 355 return | |
| 356 } | |
| 357 | |
| 358 err = this.writePairs(FCGI_PARAMS, p) | |
| 359 if err != nil { | |
| 360 return | |
| 361 } | |
| 362 | |
| 363 body := newWriter(this, FCGI_STDIN) | |
| 364 if req != nil { | |
| 365 io.Copy(body, req) | |
| 366 } | |
| 367 body.Close() | |
| 368 | |
| 369 r = &streamReader{c: this} | |
| 370 return | |
| 371 } | |
| 372 | |
| 373 type badStringError struct { | |
| 374 what string | |
| 375 str string | |
| 376 } | |
| 377 | |
| 378 func (e *badStringError) Error() string { return fmt.Sprintf("%s %q", e.what, e.str) } | |
| 379 | |
| 380 // Request returns a HTTP Response with Header and Body | |
| 381 // from fcgi responder | |
| 382 func (this *FCGIClient) Request(p map[string]string, req io.Reader) (resp *http.Response, err error) { | |
| 383 | |
| 384 r, err := this.Do(p, req) | |
| 385 if err != nil { | |
| 386 return | |
| 387 } | |
| 388 | |
| 389 rb := bufio.NewReader(r) | |
| 390 tp := textproto.NewReader(rb) | |
| 391 resp = new(http.Response) | |
| 392 // Parse the first line of the response. | |
| 393 line, err := tp.ReadLine() | |
| 394 if err != nil { | |
| 395 if err == io.EOF { | |
| 396 err = io.ErrUnexpectedEOF | |
| 397 } | |
| 398 return nil, err | |
| 399 } | |
| 400 if i := strings.IndexByte(line, ' '); i == -1 { | |
| 401 err = &badStringError{"malformed HTTP response", line} | |
| 402 } else { | |
| 403 resp.Proto = line[:i] | |
| 404 resp.Status = strings.TrimLeft(line[i+1:], " ") | |
| 405 } | |
| 406 statusCode := resp.Status | |
| 407 if i := strings.IndexByte(resp.Status, ' '); i != -1 { | |
| 408 statusCode = resp.Status[:i] | |
| 409 } | |
| 410 if len(statusCode) != 3 { | |
| 411 err = &badStringError{"malformed HTTP status code", statusCode} | |
| 412 } | |
| 413 resp.StatusCode, err = strconv.Atoi(statusCode) | |
| 414 if err != nil || resp.StatusCode < 0 { | |
| 415 err = &badStringError{"malformed HTTP status code", statusCode} | |
| 416 } | |
| 417 var ok bool | |
| 418 if resp.ProtoMajor, resp.ProtoMinor, ok = http.ParseHTTPVersion(resp.Proto); !ok { | |
| 419 err = &badStringError{"malformed HTTP version", resp.Proto} | |
| 420 } | |
| 421 // Parse the response headers. | |
| 422 mimeHeader, err := tp.ReadMIMEHeader() | |
| 423 if err != nil { | |
| 424 if err == io.EOF { | |
| 425 err = io.ErrUnexpectedEOF | |
| 426 } | |
| 427 return nil, err | |
| 428 } | |
| 429 resp.Header = http.Header(mimeHeader) | |
| 430 // TODO: fixTransferEncoding ? | |
| 431 resp.TransferEncoding = resp.Header["Transfer-Encoding"] | |
| 432 resp.ContentLength, _ = strconv.ParseInt(resp.Header.Get("Content-Length"), 10, 64) | |
| 433 | |
| 434 if chunked(resp.TransferEncoding) { | |
| 435 resp.Body = ioutil.NopCloser(httputil.NewChunkedReader(rb)) | |
| 436 } else { | |
| 437 resp.Body = ioutil.NopCloser(rb) | |
| 438 } | |
| 439 return | |
| 440 } | |
| 441 | |
| 442 // Get issues a GET request to the fcgi responder. | |
| 443 func (this *FCGIClient) Get(p map[string]string) (resp *http.Response, err error) { | |
| 444 | |
| 445 p["REQUEST_METHOD"] = "GET" | |
| 446 p["CONTENT_LENGTH"] = "0" | |
| 447 | |
| 448 return this.Request(p, nil) | |
| 449 } | |
| 450 | |
| 451 // Get issues a Post request to the fcgi responder. with request body | |
| 452 // in the format that bodyType specified | |
| 453 func (this *FCGIClient) Post(p map[string]string, bodyType string, body io.Reader, l int) (resp *http.Response, err error) { | |
| 454 | |
| 455 if len(p["REQUEST_METHOD"]) == 0 || p["REQUEST_METHOD"] == "GET" { | |
| 456 p["REQUEST_METHOD"] = "POST" | |
| 457 } | |
| 458 p["CONTENT_LENGTH"] = strconv.Itoa(l) | |
| 459 if len(bodyType) > 0 { | |
| 460 p["CONTENT_TYPE"] = bodyType | |
| 461 } else { | |
| 462 p["CONTENT_TYPE"] = "application/x-www-form-urlencoded" | |
| 463 } | |
| 464 | |
| 465 return this.Request(p, body) | |
| 466 } | |
| 467 | |
| 468 // PostForm issues a POST to the fcgi responder, with form | |
| 469 // as a string key to a list values (url.Values) | |
| 470 func (this *FCGIClient) PostForm(p map[string]string, data url.Values) (resp *http.Response, err error) { | |
| 471 body := bytes.NewReader([]byte(data.Encode())) | |
| 472 return this.Post(p, "application/x-www-form-urlencoded", body, body.Len()) | |
| 473 } | |
| 474 | |
| 475 // PostFile issues a POST to the fcgi responder in multipart(RFC 2046) standard, | |
| 476 // with form as a string key to a list values (url.Values), | |
| 477 // and/or with file as a string key to a list file path. | |
| 478 func (this *FCGIClient) PostFile(p map[string]string, data url.Values, file map[string]string) (resp *http.Response, err error) { | |
| 479 buf := &bytes.Buffer{} | |
| 480 writer := multipart.NewWriter(buf) | |
| 481 bodyType := writer.FormDataContentType() | |
| 482 | |
| 483 for key, val := range data { | |
| 484 for _, v0 := range val { | |
| 485 err = writer.WriteField(key, v0) | |
| 486 if err != nil { | |
| 487 return | |
| 57 } | 488 } |
| 58 if err != nil { | 489 } |
| 59 return err | 490 } |
| 60 } | 491 |
| 61 } | 492 for key, val := range file { |
| 62 } | 493 fd, e := os.Open(val) |
| 63 if err := writeRecord(bw, fcgiStdin, fcgiRequestID, nil); err != nil { | 494 if e != nil { |
| 64 return err | 495 return nil, e |
| 65 } | 496 } |
| 66 if err := bw.Flush(); err != nil { | 497 defer fd.Close() |
| 67 return err | 498 |
| 68 } | 499 part, e := writer.CreateFormFile(key, filepath.Base(val)) |
| 69 | 500 if e != nil { |
| 70 // Read response | 501 return nil, e |
| 71 var stdout strings.Builder | 502 } |
| 72 br := bufio.NewReader(conn) | 503 _, err = io.Copy(part, fd) |
| 73 | 504 } |
| 74 for { | 505 |
| 75 hdr := make([]byte, 8) | 506 err = writer.Close() |
| 76 if _, err := io.ReadFull(br, hdr); err != nil { | 507 if err != nil { |
| 77 return err | 508 return |
| 78 } | 509 } |
| 79 recType := hdr[1] | 510 |
| 80 contentLen := int(binary.BigEndian.Uint16(hdr[4:6])) | 511 return this.Post(p, bodyType, buf, buf.Len()) |
| 81 paddingLen := int(hdr[6]) | 512 } |
| 82 | 513 |
| 83 content := make([]byte, contentLen) | 514 // Checks whether chunked is part of the encodings stack |
| 84 if _, err := io.ReadFull(br, content); err != nil { | 515 func chunked(te []string) bool { return len(te) > 0 && te[0] == "chunked" } |
| 85 return err | |
| 86 } | |
| 87 if paddingLen > 0 { | |
| 88 if _, err := io.ReadFull(br, make([]byte, paddingLen)); err != nil { | |
| 89 return err | |
| 90 } | |
| 91 } | |
| 92 | |
| 93 switch recType { | |
| 94 case fcgiStdout: | |
| 95 stdout.Write(content) | |
| 96 case fcgiEndRequest: | |
| 97 return forwardResponse(w, stdout.String()) | |
| 98 } | |
| 99 // fcgiStderr is silently discarded | |
| 100 } | |
| 101 } | |
| 102 | |
| 103 func forwardResponse(w http.ResponseWriter, raw string) error { | |
| 104 sep := "\r\n\r\n" | |
| 105 idx := strings.Index(raw, sep) | |
| 106 advance := 4 | |
| 107 if idx == -1 { | |
| 108 sep = "\n\n" | |
| 109 idx = strings.Index(raw, sep) | |
| 110 advance = 2 | |
| 111 } | |
| 112 if idx == -1 { | |
| 113 w.Write([]byte(raw)) | |
| 114 return nil | |
| 115 } | |
| 116 | |
| 117 headerPart := raw[:idx] | |
| 118 body := raw[idx+advance:] | |
| 119 | |
| 120 tp := textproto.NewReader(bufio.NewReader(strings.NewReader(headerPart + "\r\n\r\n"))) | |
| 121 mime, _ := tp.ReadMIMEHeader() | |
| 122 | |
| 123 status := 200 | |
| 124 if s := mime.Get("Status"); s != "" { | |
| 125 code, _, _ := strings.Cut(s, " ") | |
| 126 if n := parseIntFast(code); n > 0 { | |
| 127 status = n | |
| 128 } | |
| 129 mime.Del("Status") | |
| 130 } | |
| 131 | |
| 132 for k, vs := range mime { | |
| 133 for _, v := range vs { | |
| 134 w.Header().Add(k, v) | |
| 135 } | |
| 136 } | |
| 137 w.WriteHeader(status) | |
| 138 w.Write([]byte(body)) | |
| 139 return nil | |
| 140 } | |
| 141 | |
| 142 func writeRecord(w io.Writer, recType uint8, reqID uint16, content []byte) error { | |
| 143 length := len(content) | |
| 144 padding := (8 - (length % 8)) % 8 | |
| 145 hdr := []byte{ | |
| 146 fcgiVersion, recType, | |
| 147 byte(reqID >> 8), byte(reqID), | |
| 148 byte(length >> 8), byte(length), | |
| 149 byte(padding), 0, | |
| 150 } | |
| 151 if _, err := w.Write(hdr); err != nil { | |
| 152 return err | |
| 153 } | |
| 154 if len(content) > 0 { | |
| 155 if _, err := w.Write(content); err != nil { | |
| 156 return err | |
| 157 } | |
| 158 } | |
| 159 if padding > 0 { | |
| 160 if _, err := w.Write(make([]byte, padding)); err != nil { | |
| 161 return err | |
| 162 } | |
| 163 } | |
| 164 return nil | |
| 165 } | |
| 166 | |
| 167 func encodeParams(params map[string]string) []byte { | |
| 168 var buf []byte | |
| 169 for k, v := range params { | |
| 170 buf = appendLen(buf, len(k)) | |
| 171 buf = appendLen(buf, len(v)) | |
| 172 buf = append(buf, k...) | |
| 173 buf = append(buf, v...) | |
| 174 } | |
| 175 return buf | |
| 176 } | |
| 177 | |
| 178 func appendLen(buf []byte, n int) []byte { | |
| 179 if n <= 127 { | |
| 180 return append(buf, byte(n)) | |
| 181 } | |
| 182 return append(buf, byte((n>>24)|0x80), byte(n>>16), byte(n>>8), byte(n)) | |
| 183 } | |
| 184 | |
| 185 func parseIntFast(s string) int { | |
| 186 n := 0 | |
| 187 for i := 0; i < len(s); i++ { | |
| 188 c := s[i] | |
| 189 if c < '0' || c > '9' { | |
| 190 return 0 | |
| 191 } | |
| 192 n = n*10 + int(c-'0') | |
| 193 } | |
| 194 return n | |
| 195 } |
