使用Apache HTTP服务器来运行SCGI。(Shǐyòng Apache HTTP fúwùqì lái yùnxíng SCGI.)

SCGI(简单通用网关接口)是Web服务器和应用服务器之间的通信协议。

环境

Windows 11
Apache/2.4.57

Windows 11
Apache/2.4.57

准备

    1. 加载必要的模块。要执行SCGI脚本,需要加载mod_proxy和mod_proxy_scgi模块。取消注释httpd.conf文件的第150行附近的下面两行。

httpd.conf
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_scgi_module modules/mod_proxy_scgi.so

进行代理设置。使用ProxyPass或RewriteRule将请求转发到想要处理的URL的SCGI服务器上。

httpd.conf

ServerName app.localhost:80
ProxyPass /aaa scgi://localhost:4000

重新启动Apache HTTP服务器。

启动SCGI服务器。与CGI不同,SCGI进程需要预先启动。这里准备了下面的Python脚本。

app.py
import json
import socket
import threading

def recv_netstring(socket):
len = 0
while (ch := socket.recv(1)[0]) != ord(‘:’):
len = len * 10 + ch – ord(‘0’)
content = socket.recv(len).decode(‘UTF-8’)
socket.recv(1)
return content

def parse_headers(s):
items = s.split(‘\0’)
return { key: value for key, value in zip(items[::2], items[1::2]) }

count = 0
count_lock = threading.Lock()
def handle_request(socket):
global count, count_lock
try:
headers = parse_headers(recv_netstring(socket))
content = socket.recv(int(headers[‘CONTENT_LENGTH’])).decode(‘UTF-8’)
with count_lock:
count_temp = count
count += 1
response = json.dumps({
‘count’: count_temp,
‘content’: content,
‘headers’: headers,
})
socket.send(“””Status: 200 OK
Content-Type: application/json

{:s}”””.format(response).encode(‘UTF-8’))
finally:
socket.close()

def main():
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind((‘localhost’, 4000))
server_socket.listen(5)
print(“server start”)
while True:
client_socket, address = server_socket.accept()
print(“accept {}”.format(address))
th = threading.Thread(target=handle_request, args=(client_socket,))
th.run()

main()

python app.py

确认动作

当从浏览器访问 http://app.localhost/aaa/bbb?p=1&q=2 时,返回以下响应。

{
  "count": 0,
  "content": "",
  "headers": {
    "CONTENT_LENGTH": "0",
    "SCGI": "1",
    "HTTP_HOST": "app.localhost",
    "HTTP_USER_AGENT": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/117.0",
    "HTTP_ACCEPT": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
    "HTTP_ACCEPT_LANGUAGE": "ja,en-US;q=0.7,en;q=0.3",
    "HTTP_ACCEPT_ENCODING": "gzip, deflate, br",
    "HTTP_DNT": "1",
    "HTTP_CONNECTION": "keep-alive",
    "HTTP_UPGRADE_INSECURE_REQUESTS": "1",
    "HTTP_SEC_FETCH_DEST": "document",
    "HTTP_SEC_FETCH_MODE": "navigate",
    "HTTP_SEC_FETCH_SITE": "none",
    "HTTP_SEC_FETCH_USER": "?1",
    "PATH": // 略
    "SystemRoot": "C:\\WINDOWS",
    "COMSPEC": "C:\\WINDOWS\\system32\\cmd.exe",
    "PATHEXT": ".COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC",
    "WINDIR": "C:\\WINDOWS",
    "SERVER_SIGNATURE": "",
    "SERVER_SOFTWARE": "Apache/2.4.57 (Win64) PHP/8.2.10",
    "SERVER_NAME": "app.localhost",
    "SERVER_ADDR": "127.0.0.1",
    "SERVER_PORT": "80",
    "REMOTE_ADDR": "127.0.0.1",
    "DOCUMENT_ROOT": "C:/Apache24/htdocs",
    "REQUEST_SCHEME": "http",
    "CONTEXT_PREFIX": "",
    "CONTEXT_DOCUMENT_ROOT": "C:/Apache24/htdocs",
    "SERVER_ADMIN": "admin@example.com",
    "SCRIPT_FILENAME": "proxy:scgi://localhost/bbb",
    "REMOTE_PORT": "54741",
    "SERVER_PROTOCOL": "HTTP/1.1",
    "REQUEST_METHOD": "GET",
    "QUERY_STRING": "p=1&q=2",
    "REQUEST_URI": "/aaa/bbb?p=1&q=2",
    "SCRIPT_NAME": "/aaa/bbb"
  }
}

接下来,我使用curl将 p=1&q=2 发送到 http://app.localhost/aaa,并收到了以下响应。

{
   "count": 1,
   "content": "p=1&q=2",
   "headers": {
      "CONTENT_LENGTH": "7",
      "SCGI": "1",
      "HTTP_HOST": "app.localhost",
      "HTTP_USER_AGENT": "curl/8.0.1",
      "HTTP_ACCEPT": "*/*",
      "CONTENT_TYPE": "application/x-www-form-urlencoded",
      "PATH": // 略
      "COMSPEC": "C:\\WINDOWS\\system32\\cmd.exe",
      "PATHEXT": ".COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC",
      "WINDIR": "C:\\WINDOWS",
      "SERVER_SIGNATURE": "",
      "SERVER_SOFTWARE": "Apache/2.4.57 (Win64) PHP/8.2.10",
      "SERVER_NAME": "app.localhost",
      "SERVER_ADDR": "127.0.0.1",
      "SERVER_PORT": "80",
      "REMOTE_ADDR": "127.0.0.1",
      "DOCUMENT_ROOT": "C:/Apache24/htdocs",
      "REQUEST_SCHEME": "http",
      "CONTEXT_PREFIX": "",
      "CONTEXT_DOCUMENT_ROOT": "C:/Apache24/htdocs",
      "SERVER_ADMIN": "admin@example.com",
      "SCRIPT_FILENAME": "proxy:scgi://localhost/",
      "REMOTE_PORT": "54851",
      "SERVER_PROTOCOL": "HTTP/1.1",
      "REQUEST_METHOD": "POST",
      "QUERY_STRING": "",
      "REQUEST_URI": "/aaa",
      "SCRIPT_NAME": "/aaa"
   }
}