1 # Copyright (C) 2006-2008, Parrot Foundation.
6 examples/io/httpd.pir - HTTP server
10 $ ./parrot examples/io/httpd.pir
14 A very tiny HTTP-Server. It currently only understands the GET method.
15 It's a nice way of testing pretty much all IO functions.
16 By default (and not yet configurable) it binds to localhost:1234.
18 =head2 Serving Parrot Docs
20 If no filename is given it serves the HTML documentation
21 in ./docs/html. Make sure you have built them with
25 After that you can browse the documentation with
31 http://localhost:1234/docs/html/index.html
33 =head2 Serving Other HTML Files
35 If a html file is present in the request, this file will be served:
37 http://localhost:1234/index.html
39 This will sent F<./index.html> from the directory, where F<httpd.pir>
44 If the file extension is C<.pir> or C<.pbc>, this file will be loaded
45 below the directory F<cgi-pir> and the function C<cgi_main> will be
46 invoked with the query as an argument.
47 This functions should return a plain string, which will be sent to the
50 F<cgi_main> is called with 3 arguments: a todo/reserved PMC, a string
51 with the original query and a Hash, with C<key=value> items split by
52 C<'+'>. C<key> and C<value> are already C<urldecoded>.
56 .param pmc reserved # TODO
57 .param string query # all after '?': "foo=1+bar=A"
58 .param pmc query_hash # Hash { foo=>'1', bar=>'A' }
59 .return ("<p>foo</p>") # in practice use a full <html>doc</html>
60 # unless serving XMLHttpRequest's
65 http://localhost:1234/foo.pir?foo=1+bar=%61
67 will serve, whatever the C<cgi_main> function returned.
71 make it work on W32/IE
73 Transcode the received string to ascii, in order to have access to an
74 implemented 'index' op. Or just use unicode instead.
82 Original author is Markus Amsler - <markus.amsler@oribi.org>
83 The code was heavily hacked by bernhard and leo.
87 .const string CRLF = "\r\n"
88 .const string CRLFCRLF = "\r\n\r\n"
89 .const string LFLF = "\n\n"
90 .const string CRCR = "\r\r"
92 .const string SERVER_NAME = "Parrot-httpd/0.1"
95 .include 'except_types.pasm'
96 .include 'socket.pasm'
99 .local pmc listener, work, fp
100 .local pmc fp # read requested files from disk
104 .local string buf, req, rep, temp
105 .local string meth, url, file_content
107 .local int len, pos, occ1, occ2, dotdot
109 .local string doc_root
114 # TODO provide sys/socket constants
115 listener = new 'Socket'
116 listener.'socket'(.PIO_PF_INET, .PIO_SOCK_STREAM, .PIO_PROTO_TCP) # PF_INET, SOCK_STREAM, tcp
117 unless listener goto ERR_NO_SOCKET
119 # Pack a sockaddr_in structure with IP and port
120 address = listener.'sockaddr'(host, port)
121 ret = listener.'bind'(address)
122 if ret == -1 goto ERR_bind
124 print "Running webserver on port "
129 print "The Parrot documentation can now be accessed at http://"
134 print "Be sure that the HTML docs have been generated with 'make html'.\n"
138 work = listener.'accept'()
146 # print "\ncharset of buf: "
150 # print "\nafter buf"
153 if ret <= 0 goto SERVE_REQ
155 index pos, req, CRLFCRLF
158 if pos >= 0 goto SERVE_REQ
162 if pos >= 0 goto SERVE_REQ
166 if pos >= 0 goto SERVE_REQ
173 .local string response
175 response = '500 Internal Server Error'
177 headers['Server'] = SERVER_NAME
182 substr meth, req, 0, occ1
184 index occ2, req, " ", occ1
186 substr url, req, occ1, len
188 if meth == "GET" goto SERVE_GET
190 print "unknown method:'"
198 (is_cgi, file_content, len) = check_cgi(url)
199 if is_cgi goto SERVE_blob
204 # Security: Don't allow access to the parent dir
205 index dotdot, url, ".."
206 if dotdot >= 0 goto SERVE_404
208 # redirect instead of serving index.html
209 if url == "/" goto SERVE_docroot
211 # Those little pics in the URL field or in tabs
212 if url == "/favicon.ico" goto SERVE_favicon
214 # try to serve a file
218 # try to open the file in url
219 concat url, doc_root, url
221 eh = new 'ExceptionHandler'
222 set_addr eh, handle_404_exception
223 eh.'handle_types'(.EXCEPTION_PIO_ERROR)
227 unless fp goto SERVE_404
228 len = stat url, .STAT_FILESIZE
229 read file_content, fp, len
233 send_response(work, response, headers, file_content)
234 # TODO provide a log method
235 print "served file '"
241 response = '301 Moved Permanently'
242 headers['Location'] = '/docs/html/index.html'
243 file_content = "Please go to <a href='docs/html/index.html'>Parrot Documentation</a>."
244 send_response(work, response, headers, file_content)
245 print "Redirect to 'docs/html/index.html'\n"
249 url = urldecode( '/docs/resources/favicon.ico')
252 handle_404_exception:
256 say "Trapped file not found exception."
260 response = '404 Not found'
261 file_content = response
262 send_response(work, response, headers, file_content)
263 print "File not found: '"
269 print "Could not open socket.\n"
270 print "Did you enable PARROT_NET_DEVEL in include/io_private.h?\n"
273 print "bind failed\n"
280 # send_response(socket, response_code, headers, body)
281 # sends HTTP response to the socket and closes the socket afterwards.
287 .local string rep, temp, headername
289 .local pmc headers_iter
293 rep .= "Connection: close"
295 ret = exists headers['Content-Length']
296 if ret goto SKIP_CONTENT_LENGTH
298 temp = to_string (len)
299 headers['Content-Length'] = temp
302 headers_iter = iter headers
304 headername = shift headers_iter
307 temp = headers[headername]
310 if headers_iter goto HEADER_LOOP
314 ret = sock.'send'(rep)
320 .param pmc args :slurpy
323 ret = sprintf "%d", args
327 # convert %xx to char
331 .local string out, char_in, char_out
332 .local int c_out, pos_in, len
339 if pos_in >= len goto END
340 substr char_in, in, pos_in, 1
342 if char_in != "%" goto INC_IN
343 # OK this was a escape character, next two are hexadecimal
345 substr hex, in, pos_in, 2
346 c_out = hex_to_int (hex)
360 .tailcall hex.'to_int'(16)
363 # if file is *.pir or *.pbc run it as CGI
366 $I0 = index url, ".pir"
367 if $I0 > 0 goto cgi_1
368 $I0 = index url, ".pbc"
369 if $I0 > 0 goto cgi_1
372 # file.pir?foo=1+bar=2
374 if $I0 == -1 goto no_query
375 .local string file, query
376 .local pmc query_hash
377 file = substr url, 0, $I0
379 query = substr url, $I0
380 # TODO split into a hash, then decode parts
381 query_hash = make_query_hash(query)
382 query = urldecode(query)
387 query_hash = new 'Hash'
390 file = urldecode(file)
392 # Security: Don't allow access to the parent dir
394 index dotdot, file, ".."
395 if dotdot < 0 goto cgi_file
404 file = "cgi-pir/" . file
410 result = 'cgi_main'($P0, query, query_hash)
412 .return (1, result, $I0)
415 # split query at '+', make hash from foo=bar items
417 .param string query # the unescapced one
418 .local pmc query_hash, items
419 .local string kv, k, v
420 query_hash = new 'Hash'
421 items = split '+', query
428 if $I0 == -1 goto no_val
429 k = substr kv, 0, $I0
443 if i < n goto lp_items
451 # vim: expandtab shiftwidth=4 ft=pir: