用Python写了一个WebServer

用 Python 写了一个 WebServer , 有 BaseHTTPServer 可用, 但是用这个的感觉就像我用 vb.net 里面的 HttpListener , 因为我已经用这个写过一个 vb.net 的HttpServer

test: http://new.twd2.net:88

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
#!/usr/bin/python
import sys, socket, threading, os, urllib, mimetypes, time
import fastcgi, getopt
 
server_name='wds/1.0'
listen_bind=('0.0.0.0', 0)
conn_timeout=60
conn_list=[]
datablock=8192 *5
host_list={}
max_connection=1000
 
 
class HttpConnection(threading.Thread):
    def __init__(self, fd, addr):
        threading.Thread.__init__(self)
        self.server_name=server_name
        self.fd=fd
        self.addr=addr
        self.running=True
        self.alive_time=time.time()
        self.req={}
        self.serv_addr=listen_bind[0]
        self.serv_port=listen_bind[1]
        #print(addr[0])
 
    def sendfile(self, filename, offset=0, maxend=-1):
        filesize=os.path.getsize(filename)
        if maxend>-1:
            filesize=min(filesize, maxend)
        fread=offset
        fileh=open(filename, 'rb')
        fileh.seek(offset)
        while fread+datablock<filesize:
            self.alive_time=time.time()
            buff=fileh.read(datablock)
            self.fd.send(buff)
            fread+=datablock
        else:
            self.alive_time=time.time()
            buff=fileh.read(filesize-fread)
            self.fd.send(buff)
             
    def kill(self):
        self.running=False
        self.fd.shutdown(socket.SHUT_RDWR)
         
    def readline(self):
        r=''
        while r.find('\n')<0:
            r+=self.fd.recv(1)
        return r   
             
    def getrequest(self):
        #global method, req_path, httpver, req
        #fdfile=self.fd.makefile()
        mrh=self.readline()#fdfile.readline()
        if mrh=='':
            self.kill()
            return
        mrh=mrh.replace('\r', '')
        mrh=mrh.replace('\n', '')
        mrh=mrh.split(' ')
        #print(mrh)
        if len(mrh)<=1:
            self.kill()
            return
        self.method=mrh[0].upper()
        self.req_path=mrh[1]
        self.uri=self.req_path
        self.httpver=mrh[2]
        self.req={}
        #parse request data
        while True:
            self.alive_time=time.time()
            r=self.readline()#fdfile.readline()
            r=r.replace('\r', '')
            r=r.replace('\n', '')
            #print(r)
            if r=='':
                break
            r=r.split(':', 1)
            t=r[0]
            v=r[1].strip()
            self.req[t.lower()]=v
        if 'host' in self.req and ':' in self.req['host']:
            self.req['host']=self.req['host'][0:self.req['host'].find(':')]
             
    def abspath(self, root, path):
        os.chdir(root)
        if path[0:1]=='/':
            path=path[1:len(path)]
        r=os.path.abspath(path)
        if r.find(root)!=0:
            return root
        return r
         
    def sendresponse(self, res, code, des):
        #fdfile=self.fd.makefile()
        self.fd.send('HTTP/1.1 ' + str(code) + ' ' + des + '\r\n')
        #print(res)
        for t in res:
            #print(t)
            v=res[t]
            #print(v)
            #print(t, v)
            self.fd.send(t + ': ' + v + '\r\n')
        self.fd.send('\r\n')
         
    def sendtext(self, msg):
        fdfile=self.fd.makefile()
        fdfile.write(msg)
 
    def run(self):
        try:
            while self.running:
                self.res={'Server': server_name, 'Cache-Control': 'private'}
                code=200
                des='OK'
                if len(conn_list)>max_connection:
                    code=503
                    des='Service Unavailable'
                    txt=des
                    self.res['Content-Type']='text/html'
                    self.res['Content-Length']=str(len(txt))
                    self.sendresponse(self.res, code, des)
                    self.sendtext(txt)
                    break
                #recv=self.fd.recv(1024)
                self.getrequest()
                 
                if self.httpver!='HTTP/1.1':
                    code=505
                    des='HTTP Version Not Supported'
                    txt=des
                    self.res['Content-Type']='text/html'
                    self.res['Content-Length']=str(len(txt))
                    self.sendresponse(self.res, code, des)
                    self.sendtext(txt)
                    self.kill()
                    break
                 
                if not self.running:
                    return
                if 'host' in self.req and self.req['host'] in host_list:
                    host=host_list[self.req['host']]
                elif '*' in host_list:
                    host=host_list['*']
                else:
                    code=400
                    des='Bad Request(Invalid Hostname)'
                    txt=des
                    self.res['Content-Type']='text/html'
                    self.res['Content-Length']=str(len(txt))
                    self.sendresponse(self.res, code, des)
                    self.sendtext(txt)
                    continue
                #print(host)
                #print(req_path)
                id=self.req_path.find('?')
                self.querystring=''
                if id>=0:
                    self.querystring=self.req_path[id+1:len(self.req_path)]
                    self.req_path=self.req_path[0:id]
                     
                     
                #print(self.req_path)
                if os.path.isdir(self.abspath(host['path'], self.req_path)) and self.req_path[len(self.req_path)-1:len(self.req_path)]!='/':
                    self.res['Location']='http://' + self.req['host']
                    if listen_bind[1]!=80:
                        self.res['Location']+=':' + str(listen_bind[1])
                    self.res['Location']+=self.req_path + '/'
                    if self.querystring!='':
                        self.res['Location']+='?' + self.querystring
                    code=301
                    des='Moved Permanently'
                    self.res['Content-Length']='0'
                    self.sendresponse(self.res, code, des)
                    continue
                 
                self.req_path=urllib.unquote(self.req_path)
                self.req_path=self.abspath(host['path'], self.req_path)
                #print(self.req_path)
 
                 
                if os.path.isdir(self.req_path):
                    #looking for default file
                    for defile in host['default']:
                        if(self.abspath(self.req_path, defile)):
                            self.req_path=self.abspath(self.req_path, defile)
                            break
                elif not os.path.isfile(self.req_path):
                    script=host['path']
                    filelist=self.req_path[len(host['path']):len(self.req_path)].split('/')
                    for file in filelist:
                        if file=='':
                            continue
                        script+='/' + file
                        #print(script)
                        if os.path.isfile(script) and 'application/x-httpd-php' in mimetypes.guess_type(script):
                            self.req_path=script
                            break
                self.root=os.path.dirname(self.req_path)
                if self.root[len(self.root)-1:len(self.root)]!='/':
                    self.root+='/'
                self.root=self.root[len(host['path']):len(self.root)]
                #print(self.req_path, self.root)
                #print(self.req_path)
                if os.path.isfile(self.req_path) and mimetypes.guess_type(self.req_path)[0]!=None:
                    if 'application/x-httpd-php' in mimetypes.guess_type(self.req_path):
                        #if 'expect' in self.req:
                        #    exp=self.req['expect'].split('-', 1)
                        #    code=int(exp[0])
                        #    des=exp[1].capitalize()
                        #    self.sendresponse({}, code, des)
                             
                        self.script_name=self.req_path[len(host['path']):len(self.req_path)]
                        self.res['Cache-Control']='no-cache'
                        fcgi=fastcgi.fastcgi(('127.0.0.1', 9000), self)
                        fcgi.process()
                        break
                    filestat=os.stat(self.req_path)
                    last_modified=time.localtime(filestat.st_mtime)
                    self.res['Last-Modified']=time.strftime('%a, %d %b %Y %H:%M:%S GMT', last_modified)
                    if 'if-modified-since' in self.req:
                        #print(res['Last-Modified'], self.req['if-modified-since'])
                        if self.res['Last-Modified']==self.req['if-modified-since']:
                            code=304
                            des='Not modified'
                            self.res['Content-Length']='0'
                            self.res['Content-Type']=mimetypes.guess_type(self.req_path)[0]
                            self.sendresponse(self.res, code, des)
                            continue
                    if self.method!='GET':
                        postlen=int(self.req['content-length'])
                        code=405
                        des='Method Not Allowed'
                        txt=des
                        self.req_path=self.abspath(host['path'], host['500'])
                        #self.fd.send(b'''HTTP/1.1 500 Not supported method\nServer: WDServer\nContent-Type: text/html\nContent-Length: 20\n\nNot supported method''')
                        #self.fd.close()
                        #return
                else:
                    code=404
                    des='Not found'
                    txt=des
                    self.req_path=self.abspath(host['path'], host['404'])
                     
                if os.path.isfile(self.req_path):
                    offset=0
                    maxend=-1
                    self.res['Content-Type']=mimetypes.guess_type(self.req_path)[0]
                    self.res['Content-Length']=str(os.path.getsize(self.req_path))
                    if 'range' in self.req:
                        rdata=self.req['range'].split('=', 1)
                        if rdata[0]=='bytes':
                            rng=rdata[1].split('-', 1)
                            offset=int(rng[0])
                            if rng[1]!='':
                                maxend=int(rng[1])
                        if maxend>-1:
                            self.res['Content-Length']=str(maxend-offset+1)
                            self.res['Content-Range']='bytes ' + str(offset) + '-' + str(maxend) + '/' + str(os.path.getsize(self.req_path))
                            code=206
                            des='Partial Content'
                    self.sendresponse(self.res, code, des)
                    self.sendfile(self.req_path, offset, maxend)
                else:
                    #txt='.conf error!'
                    self.res['Content-Type']='text/html'
                    self.res['Content-Length']=str(len(txt))
                    self.sendresponse(self.res, code, des)
                    self.sendtext(txt)
                if not 'connection' in self.req or self.req['connection'].lower()!='keep-alive':
                    self.kill()
                    return
            #self.fd.close()
        except:
            print('err')
        try:
            self.kill()
        except:
            return
def listen():
    global conn_list
     
    http_listener=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    http_listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    http_listener.bind(listen_bind)
    http_listener.listen(1024)
    conn_lock=threading.Lock()
    while(True):
        conn_fd, addr=http_listener.accept()
        conn_lock.acquire()
        conn=HttpConnection(conn_fd, addr)
        conn.start()
        try:
            now=time.time()
            for conn in conn_list:
                if now-conn.alive_time > conn_timeout:
                    if conn.running:
                        conn.kill()
            conn_list=[conn for conn in conn_list if conn.running]
            #print(conn_list)
            #break
        except:
            print(sys.exc_info())
        conn_list.append(conn)
        conn_lock.release()
    http_listener.close()
     
def load_config():
    global listen_bind
    confile=open('WS.conf', 'r')
    lines=confile.readlines()
    bind=lines[0]
    bind=bind.replace('\r', '')
    bind=bind.replace('\n', '')
    bind=bind.split(' ')
    del lines[0]
    listen_bind=(bind[0], int(bind[1]))
    for host in lines:
        host=host.replace('\r', '')
        host=host.replace('\n', '')
        host=host.split(' ')
        thishost={'name': host[0], 'path': os.path.abspath(host[2])}
        if os.path.isfile(thishost['path'] + '/' + host[0] + '.conf'):
            subconf=open(thishost['path'] + '/' + host[0] + '.conf', 'r')
            sublines=subconf.readlines()
            for i in range(0, 3):
                sublines[i]=sublines[i].replace('\r', '')
                sublines[i]=sublines[i].replace('\n', '')
            thishost['default']=sublines[0].split(', ')
            thishost['404']=sublines[1]
            thishost['500']=sublines[2]
        else:
            thishost['default']={'index.htm', 'index.html', 'index.php'}
            thishost['404']=''
            thishost['500']=''
        host_list[host[1]]=thishost
    #print(host_list)
     
def start():
    pid=0#os.fork()
    if pid==0:
        try:
            load_config()
            listen()
        except KeyboardInterrupt:
            print('Exit()')
            for conn in conn_list:
                if conn.running:
                    try:
                        conn.kill()
                    except:
                        print(sys.exc_info())
            exit()
    else:
        print('PID: ' + str(pid))
        fo=open('wds.pid', 'w')
        fo.write(str(pid))
        fo.close()
        #sys.stdin.read(1)
        #os.system('kill -9 ' + str(pid))
        #print('Killed')
     
def main():
    try:
        opts, args=getopt.getopt(sys.argv[1:], 'sp', ['start', 'stop'])
        for n, v in opts:
            if n in ('-s', '--start'):
                start()
            if n in ('-p', '--stop'):
                pid='0'
                if os.path.isfile('wds.pid'):
                    fo=open('wds.pid', 'r')
                    pid=fo.readline()
                    fo.close
                if pid!='0':
                    os.system('kill -9 ' + pid)
                    print('Killed: ' + pid)
                else:
                    print('Not running.')
                fo=open('wds.pid', 'w')
                fo.write('0')
                fo.close()
    except:
        print(sys.exc_info())
 
 
if __name__=='__main__':
    main()
发表评论?

5 条评论。

  1. 唉,我没有写代码的时间。。。
    羡慕你能有足够的时间写代码。

发表评论

注意 - 你可以用以下 HTML tags and attributes:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

:wink: :twisted: :roll: :oops: :mrgreen: :lol: :idea: :evil: :cry: :arrow: :?: :-| :-x :-o :-P :-D :-? :) :( :!: 8-O 8)

本文链接:https://twd2.me/archives/1677QrCode