mirror of
https://github.com/tnb-labs/panel.git
synced 2026-10-16 15:42:54 +08:00
118 lines
2.8 KiB
Go
118 lines
2.8 KiB
Go
package fastcgi
|
||
|
||
import (
|
||
"bytes"
|
||
"context"
|
||
"encoding/binary"
|
||
"fmt"
|
||
"io"
|
||
"net"
|
||
)
|
||
|
||
const (
|
||
typeBeginRequest byte = 1
|
||
typeEndRequest byte = 3
|
||
typeParams byte = 4
|
||
typeStdin byte = 5
|
||
typeStdout byte = 6
|
||
typeStderr byte = 7
|
||
|
||
roleResponder = 1
|
||
)
|
||
|
||
// Request 向 FastCGI 服务发起一次 responder 请求,返回剥离 CGI 响应头后的 body
|
||
func Request(ctx context.Context, network, address string, params map[string]string) ([]byte, error) {
|
||
var dialer net.Dialer
|
||
conn, err := dialer.DialContext(ctx, network, address)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
defer func() { _ = conn.Close() }()
|
||
if deadline, ok := ctx.Deadline(); ok {
|
||
_ = conn.SetDeadline(deadline)
|
||
}
|
||
|
||
// BEGIN_REQUEST:responder 角色,连接不复用
|
||
if err = writeRecord(conn, typeBeginRequest, []byte{0, roleResponder, 0, 0, 0, 0, 0, 0}); err != nil {
|
||
return nil, err
|
||
}
|
||
var buf bytes.Buffer
|
||
for name, value := range params {
|
||
encodeNameValue(&buf, name, value)
|
||
}
|
||
if err = writeRecord(conn, typeParams, buf.Bytes()); err != nil {
|
||
return nil, err
|
||
}
|
||
if err = writeRecord(conn, typeParams, nil); err != nil {
|
||
return nil, err
|
||
}
|
||
if err = writeRecord(conn, typeStdin, nil); err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
var stdout, stderr bytes.Buffer
|
||
header := make([]byte, 8)
|
||
for {
|
||
if _, err = io.ReadFull(conn, header); err != nil {
|
||
return nil, err
|
||
}
|
||
contentLength := binary.BigEndian.Uint16(header[4:6])
|
||
content := make([]byte, int(contentLength)+int(header[6]))
|
||
if _, err = io.ReadFull(conn, content); err != nil {
|
||
return nil, err
|
||
}
|
||
switch header[1] {
|
||
case typeStdout:
|
||
stdout.Write(content[:contentLength])
|
||
case typeStderr:
|
||
stderr.Write(content[:contentLength])
|
||
case typeEndRequest:
|
||
// 剥离 CGI 响应头
|
||
body := stdout.Bytes()
|
||
if _, rest, found := bytes.Cut(body, []byte("\r\n\r\n")); found {
|
||
body = rest
|
||
}
|
||
if len(body) > 0 {
|
||
return body, nil
|
||
}
|
||
if stderr.Len() > 0 {
|
||
return nil, fmt.Errorf("fastcgi stderr: %s", stderr.String())
|
||
}
|
||
return body, nil
|
||
}
|
||
}
|
||
}
|
||
|
||
// writeRecord 写入一条 FastCGI 记录
|
||
func writeRecord(w io.Writer, typ byte, content []byte) error {
|
||
header := [8]byte{1, typ}
|
||
binary.BigEndian.PutUint16(header[2:4], 1)
|
||
binary.BigEndian.PutUint16(header[4:6], uint16(len(content))) //nolint:gosec
|
||
if _, err := w.Write(header[:]); err != nil {
|
||
return err
|
||
}
|
||
if len(content) > 0 {
|
||
if _, err := w.Write(content); err != nil {
|
||
return err
|
||
}
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// encodeNameValue 按 FastCGI name-value 格式编码
|
||
func encodeNameValue(buf *bytes.Buffer, name, value string) {
|
||
writeLength := func(n int) {
|
||
if n < 128 {
|
||
buf.WriteByte(byte(n)) //nolint:gosec
|
||
return
|
||
}
|
||
var b [4]byte
|
||
binary.BigEndian.PutUint32(b[:], uint32(n)|1<<31) //nolint:gosec
|
||
buf.Write(b[:])
|
||
}
|
||
writeLength(len(name))
|
||
writeLength(len(value))
|
||
buf.WriteString(name)
|
||
buf.WriteString(value)
|
||
}
|