Introduce TinyHttp server
[lcapit-junk-code.git] / http / tiny-http
blobdbc4c0dcbceb829985362d24b6e996eb3be057da
1 #!/usr/bin/python
3 # A Tiny HTTP Server: a stupid project to learn about HTTP.
5 # Status: can retrieve a few things :)
7 # TODO:
9 # - Better HTTP error handling
10 # - Which are mandatory? Which are not?
11 # - Interpret client's headers
13 # Luiz Capitulino <lcapitulino@gmail.com>
15 from sys import argv,exit
16 import socket, os, time, mimetypes
18 class HttpRequestMethod:
19 HEAD = 1
20 GET = 2
21 POST = 3
22 PUT = 4
23 TRACE= 5
24 OPTIONS = 6
25 CONNECT = 7
26 DELETE = 8
28 class HttpResource:
29 def __get_length(self):
30 return os.path.getsize(self.path)
32 def __get_content(self):
33 f = open(self.path)
34 content = f.read()
35 f.close()
36 return content
38 def __get_content_type(self):
39 ctype = mimetypes.guess_type(self.path)[0]
40 if not ctype:
41 ctype = 'application/octet-stream'
42 return ctype
44 def __build_resource(self, path):
45 self.path = path
46 try:
47 self.length = self.__get_length()
48 except:
49 # XXX: all errors? really?
50 self.error = True
51 return
52 self.error = False
53 self.content = self.__get_content()
54 self.content_type = self.__get_content_type()
56 def __init__(self, root, abs_path):
57 path = root + '/' + abs_path
58 self.__build_resource(path)
60 class HttpResponse:
61 def __build_response(self, resource):
62 resp = ''
63 time_str = time.strftime('%a, %d %b %Y %H:%M:%S GMT', time.gmtime())
64 if resource.error:
65 # FIXME: There are number of possible errors, but we generate
66 # 404 only
67 resp += 'HTTP/1.1 404 Not Found\r\n'
68 else:
69 resp += 'HTTP/1.1 200 OK\r\n'
70 resp += 'Date: %s\r\n' % time_str
71 resp += 'Server: TinyHttp/0.1\r\n'
72 resp += 'Connection: close\r\n'
73 if not resource.error:
74 resp += 'Content-Length: %d\r\n' % resource.length
75 resp += 'Content-Type: %s\r\n' % resource.content_type
76 resp += '\r\n'
77 resp += resource.content
78 self.response = resp
80 def __init__(self, resource):
81 self.__build_response(resource)
83 class HttpRequest:
84 def __get_method(self, type_str):
85 if type_str == 'HEAD':
86 return HttpRequestMethod.HEAD
87 elif type_str == 'GET':
88 return HttpRequestMethod.GET
89 elif type_str == 'POST':
90 return HttpRequestMethod.POST
91 elif type_str == 'PUT':
92 return HttpRequestMethod.PUT
93 elif type_str == 'TRACE':
94 return HttpRequestMethod.TRACE
95 elif type_str == 'OPTIONS':
96 return HttpRequestMethod.OPTIONS
97 elif type_str == 'CONNECT':
98 return HttpRequestMethod.CONNECT
99 elif type_str == 'DELETE':
100 return HttpRequestMethod.DELETE
102 def __get_version(self, ver_str):
103 if ver_str == 'HTTP/1.0':
104 return 10
105 elif ver_str == 'HTTP/1.1':
106 return 11
108 def __parse_request(self, buf):
109 in_request = True
110 (method, resource, version) = range(0, 3)
111 req_pos = method
112 headers_list = []
113 header_cur = ''
114 met_str = uri_str = ver_str = ''
115 for c in buf:
116 if c == '\r':
117 continue
118 if in_request:
119 if c == ' ':
120 req_pos += 1
121 continue
122 elif c == '\n':
123 in_request = False
124 continue
125 if req_pos == method:
126 met_str += c
127 elif req_pos == resource:
128 uri_str += c
129 elif req_pos == version:
130 ver_str += c
131 else:
132 if c == '\n':
133 if header_cur:
134 headers_list.append(header_cur)
135 header_cur = ''
136 continue
137 header_cur += c
138 self.headers = headers_list
139 self.uri = uri_str
140 self.method = self.__get_method(met_str)
141 self.version = self.__get_version(ver_str)
143 def __repr__(self):
144 s = '%d %s %d\n' % (self.method, self.uri, self.version)
145 for header in self.headers:
146 s += header + '\n'
147 return s
149 def __init__(self, buf):
150 self.__parse_request(buf)
152 class TinyHttp:
153 def __serve_request(self, request, csock):
154 assert request.method == HttpRequestMethod.GET
155 resource = HttpResource(self.root, request.uri)
156 resp = HttpResponse(resource)
157 csock.sendall(resp.response)
158 print '-> served: ' + request.uri
160 def __read_request(self, csock):
161 buf = ''
162 while True:
163 buf += csock.recv(4096)
164 if buf.endswith('\r\n\r\n'):
165 return HttpRequest(buf)
167 def __create_sock(self, domain, port):
168 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
169 s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
170 s.bind((domain, port))
171 s.listen(1)
172 return s
174 def main_loop(self):
175 while True:
176 (csock, addr) = self.sock.accept()
177 print '-> Connection from: %s:%d' % (addr[0],addr[1])
178 r = self.__read_request(csock)
179 assert r
180 self.__serve_request(r, csock)
181 csock.close()
182 print
184 def __init__(self, domain, port, root):
185 self.root = root
186 self.sock = self.__create_sock(domain, port)
188 def main():
189 if len(argv) != 4:
190 print 'tiny-http <domain> <port> <root>'
191 exit(1)
193 server = TinyHttp(argv[1], int(argv[2]), argv[3])
194 server.main_loop()
196 if __name__ == '__main__':
197 main()