├── .gitignore ├── FastCGIClient.py ├── README.md └── fcgi.py /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/* 2 | .idea 3 | 4 | FastCGIClient.pyc 5 | 6 | .DS_Store -------------------------------------------------------------------------------- /FastCGIClient.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import socket 4 | import random 5 | 6 | 7 | class FastCGIClient: 8 | """A Fast-CGI Client for Python""" 9 | 10 | # private 11 | __FCGI_VERSION = 1 12 | 13 | __FCGI_ROLE_RESPONDER = 1 14 | __FCGI_ROLE_AUTHORIZER = 2 15 | __FCGI_ROLE_FILTER = 3 16 | 17 | __FCGI_TYPE_BEGIN = 1 18 | __FCGI_TYPE_ABORT = 2 19 | __FCGI_TYPE_END = 3 20 | __FCGI_TYPE_PARAMS = 4 21 | __FCGI_TYPE_STDIN = 5 22 | __FCGI_TYPE_STDOUT = 6 23 | __FCGI_TYPE_STDERR = 7 24 | __FCGI_TYPE_DATA = 8 25 | __FCGI_TYPE_GETVALUES = 9 26 | __FCGI_TYPE_GETVALUES_RESULT = 10 27 | __FCGI_TYPE_UNKOWNTYPE = 11 28 | 29 | __FCGI_HEADER_SIZE = 8 30 | 31 | # request state 32 | FCGI_STATE_SEND = 1 33 | FCGI_STATE_ERROR = 2 34 | FCGI_STATE_SUCCESS = 3 35 | 36 | def __init__(self, host, port, timeout, keepalive): 37 | self.host = host 38 | self.port = port 39 | self.timeout = timeout 40 | if keepalive: 41 | self.keepalive = 1 42 | else: 43 | self.keepalive = 0 44 | self.sock = None 45 | self.requests = dict() 46 | 47 | def __connect(self): 48 | self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 49 | self.sock.settimeout(self.timeout) 50 | self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 51 | # if self.keepalive: 52 | # self.sock.setsockopt(socket.SOL_SOCKET, socket.SOL_KEEPALIVE, 1) 53 | # else: 54 | # self.sock.setsockopt(socket.SOL_SOCKET, socket.SOL_KEEPALIVE, 0) 55 | try: 56 | self.sock.connect((self.host, int(self.port))) 57 | except socket.error as msg: 58 | self.sock.close() 59 | self.sock = None 60 | print(repr(msg)) 61 | return False 62 | return True 63 | 64 | def __encodeFastCGIRecord(self, fcgi_type, content, requestid): 65 | length = len(content) 66 | return chr(FastCGIClient.__FCGI_VERSION) \ 67 | + chr(fcgi_type) \ 68 | + chr((requestid >> 8) & 0xFF) \ 69 | + chr(requestid & 0xFF) \ 70 | + chr((length >> 8) & 0xFF) \ 71 | + chr(length & 0xFF) \ 72 | + chr(0) \ 73 | + chr(0) \ 74 | + content 75 | 76 | def __encodeNameValueParams(self, name, value): 77 | nLen = len(str(name)) 78 | vLen = len(str(value)) 79 | record = '' 80 | if nLen < 128: 81 | record += chr(nLen) 82 | else: 83 | record += chr((nLen >> 24) | 0x80) \ 84 | + chr((nLen >> 16) & 0xFF) \ 85 | + chr((nLen >> 8) & 0xFF) \ 86 | + chr(nLen & 0xFF) 87 | if vLen < 128: 88 | record += chr(vLen) 89 | else: 90 | record += chr((vLen >> 24) | 0x80) \ 91 | + chr((vLen >> 16) & 0xFF) \ 92 | + chr((vLen >> 8) & 0xFF) \ 93 | + chr(vLen & 0xFF) 94 | return record + str(name) + str(value) 95 | 96 | def __decodeFastCGIHeader(self, stream): 97 | header = dict() 98 | header['version'] = ord(stream[0]) 99 | header['type'] = ord(stream[1]) 100 | header['requestId'] = (ord(stream[2]) << 8) + ord(stream[3]) 101 | header['contentLength'] = (ord(stream[4]) << 8) + ord(stream[5]) 102 | header['paddingLength'] = ord(stream[6]) 103 | header['reserved'] = ord(stream[7]) 104 | return header 105 | 106 | def __decodeFastCGIRecord(self): 107 | header = self.sock.recv(int(FastCGIClient.__FCGI_HEADER_SIZE)) 108 | if not header: 109 | return False 110 | else: 111 | record = self.__decodeFastCGIHeader(header) 112 | record['content'] = '' 113 | if 'contentLength' in record.keys(): 114 | contentLength = int(record['contentLength']) 115 | buffer = self.sock.recv(contentLength) 116 | while contentLength and buffer: 117 | contentLength -= len(buffer) 118 | record['content'] += buffer 119 | if 'paddingLength' in record.keys(): 120 | skiped = self.sock.recv(int(record['paddingLength'])) 121 | return record 122 | 123 | def request(self, nameValuePairs={}, post=''): 124 | if not self.__connect(): 125 | print('connect failure! please check your fasctcgi-server !!') 126 | return 127 | 128 | requestId = random.randint(1, (1 << 16) - 1) 129 | self.requests[requestId] = dict() 130 | request = "" 131 | beginFCGIRecordContent = chr(0) \ 132 | + chr(FastCGIClient.__FCGI_ROLE_RESPONDER) \ 133 | + chr(self.keepalive) \ 134 | + chr(0) * 5 135 | request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_BEGIN, 136 | beginFCGIRecordContent, requestId) 137 | paramsRecord = '' 138 | if nameValuePairs: 139 | for (name, value) in nameValuePairs.iteritems(): 140 | # paramsRecord = self.__encodeNameValueParams(name, value) 141 | # request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_PARAMS, paramsRecord, requestId) 142 | paramsRecord += self.__encodeNameValueParams(name, value) 143 | 144 | if paramsRecord: 145 | request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_PARAMS, paramsRecord, requestId) 146 | request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_PARAMS, '', requestId) 147 | 148 | if post: 149 | request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_STDIN, post, requestId) 150 | request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_STDIN, '', requestId) 151 | self.sock.send(request) 152 | self.requests[requestId]['state'] = FastCGIClient.FCGI_STATE_SEND 153 | self.requests[requestId]['response'] = '' 154 | return self.__waitForResponse(requestId) 155 | 156 | def __waitForResponse(self, requestId): 157 | while True: 158 | response = self.__decodeFastCGIRecord() 159 | if not response: 160 | break 161 | if response['type'] == FastCGIClient.__FCGI_TYPE_STDOUT \ 162 | or response['type'] == FastCGIClient.__FCGI_TYPE_STDERR: 163 | if response['type'] == FastCGIClient.__FCGI_TYPE_STDERR: 164 | self.requests['state'] = FastCGIClient.FCGI_STATE_ERROR 165 | if requestId == int(response['requestId']): 166 | self.requests[requestId]['response'] += response['content'] 167 | if response['type'] == FastCGIClient.FCGI_STATE_SUCCESS: 168 | self.requests[requestId] 169 | return self.requests[requestId]['response'] 170 | 171 | def __repr__(self): 172 | return "fastcgi connect host:{} port:{}".format(self.host, self.port) 173 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | Python FastCGI Client 3 | 4 | A `Python` FastCGI Client for directly access FastCGI web resource through `FastCGI` 5 | 6 | How use?(You should start your FastCGI Process) 7 | ======================== 8 | 9 | from FastCGIClient import * 10 | client = FastCGIClient('127.0.0.1', 9000, 3000, 0) 11 | params = dict() 12 | documentRoot = "/Users/baidu/php_workspace" 13 | uri = "/echo.php" 14 | content = "name=john&address=beijing" 15 | params = {'GATEWAY_INTERFACE': 'FastCGI/1.0', 16 | 'REQUEST_METHOD': 'POST', 17 | 'SCRIPT_FILENAME': documentRoot + uri, 18 | 'SCRIPT_NAME': uri, 19 | 'QUERY_STRING': '', 20 | 'REQUEST_URI': uri, 21 | 'DOCUMENT_ROOT': documentRoot, 22 | 'SERVER_SOFTWARE': 'php/fcgiclient', 23 | 'REMOTE_ADDR': '127.0.0.1', 24 | 'REMOTE_PORT': '9985', 25 | 'SERVER_ADDR': '127.0.0.1', 26 | 'SERVER_PORT': '80', 27 | 'SERVER_NAME': "localhost", 28 | 'SERVER_PROTOCOL': 'HTTP/1.1', 29 | 'CONTENT_TYPE': 'application/x-www-form-urlencoded', 30 | 'CONTENT_LENGTH': len(content) 31 | } 32 | client.request(params, content) 33 | 34 | 35 | -------------------------------------------------------------------------------- /fcgi.py: -------------------------------------------------------------------------------- 1 | from FastCGIClient import * 2 | import sys 3 | from urlparse import urlparse as parse_url 4 | 5 | 6 | def main(): 7 | argvs = sys.argv 8 | argc = len(argvs) 9 | if argc < 3: 10 | print('Usage: python fcgi.py http://127.0.0.1:9000/path/to/some.php?queryString path/to/documentroot postData') 11 | print('Example: python fcgi.py http://127.0.0.1:9000/echo.php\?name\=john ' 12 | '/Users/baidu/php_workspace name=john&address=beijing') 13 | return 14 | argv = argvs[1] 15 | documentRoot = argvs[2] 16 | parseResult = parse_url(argv) 17 | host = parseResult.hostname 18 | port = parseResult.port 19 | uri = parseResult.path 20 | query = parseResult.query 21 | client = FastCGIClient(host, port, 3000, 0) 22 | content = '' 23 | if argc > 3: 24 | content = argvs[3] 25 | # content = "name=john&address=beijing" 26 | params = {'GATEWAY_INTERFACE': 'FastCGI/1.0', 27 | 'REQUEST_METHOD': 'POST', 28 | 'SCRIPT_FILENAME': documentRoot + uri, 29 | 'SCRIPT_NAME': uri, 30 | 'QUERY_STRING': query, 31 | 'REQUEST_URI': uri, 32 | 'DOCUMENT_ROOT': documentRoot, 33 | 'SERVER_SOFTWARE': 'php/fcgiclient', 34 | 'REMOTE_ADDR': '127.0.0.1', 35 | 'REMOTE_PORT': '9985', 36 | 'SERVER_ADDR': '127.0.0.1', 37 | 'SERVER_PORT': '80', 38 | 'SERVER_NAME': "localhost", 39 | 'SERVER_PROTOCOL': 'HTTP/1.1', 40 | 'CONTENT_TYPE': 'application/x-www-form-urlencoded', 41 | 'CONTENT_LENGTH': len(content) 42 | } 43 | print(client.request(params, content)) 44 | 45 | 46 | if __name__ == '__main__': 47 | main() 48 | --------------------------------------------------------------------------------