1.1. A Simple Webserver

Start felix section to tools/webserver.flx[1 /1 ]
     1: #line 15 "./lpsrc/flx_faio_tools.pak"
     2: #import <flx.flxh>
     3: 
     4: #if POSIX
     5: include "flx_faio_posix";  // aio_ropen
     6: //open Faio_posix;
     7: #endif
     8: 
     9: 
    10: include "flx_socket";
    11: open Flx_socket;
    12: 
    13: include "flx_stream";
    14: open Flx_stream;
    15: 
    16: open TerminalIByteStream[fd_t];
    17: open TerminalIOByteStream[socket_t];
    18: 
    19: macro fun dbg(x) = { fprint (cerr,x); };
    20: 
    21: 
    22: // this is a hack to make close work on a listener
    23: // RF got this right the first time:
    24: // in the abstract a listener is NOT a socket
    25: // In fact, it is a socket server, with accept() a way to
    26: // read new sockets off it ..
    27: open TerminalIByteStream[socket_t];
    28: 
    29: header = """
    30: string
    31: getline_to_url(const string& get)
    32: {
    33:     // chomp off "GET " (should check it)
    34:     if(get.length() < 4) return "";
    35: 
    36:     std::size_t pos = get.substr(4).find(' ');
    37: 
    38:     if(pos == string::npos) return "";
    39: 
    40:     return get.substr(4, pos);
    41: }
    42: 
    43: // split url into base and file name http://foo.com/file.html
    44: // -> http://foo.com + file.html. failure returns nothing.
    45: bool
    46: split_url(const string& inurl, string& base, string& file)
    47: {
    48:     // munch leading http:// if present
    49:     string url;
    50:     if(inurl.length() >= 7 && inurl.substr(0, 7) == "http://")
    51:       url = inurl.substr(7);
    52:     else
    53:       url = inurl;
    54: 
    55:     std::size_t pos = url.find('/');
    56: 
    57:     if(string::npos == pos)  return false;       // all bad
    58: 
    59:     base = url.substr(0, pos);
    60:     file = url.substr(pos+1);
    61:     return true;            // all good
    62: }
    63: 
    64: bool
    65: split_getline(const string& get, string& base, string& file)
    66: {
    67:     return split_url(getline_to_url(get), base, file);
    68: }
    69: """;
    70: 
    71: proc parse_get_line: string*lvalue[bool]*lvalue[string]*lvalue[string]
    72:  = '$2 = split_getline($1, $3, $4);';
    73: 
    74: fun substr: string*int -> string = "$1.substr($2)";
    75: 
    76: // TODO: fill in that length field, stream back the requested jpeg,
    77: // get port from argv.
    78: 
    79: val html_header = """HTTP/0.9 200 OK\r
    80: Date: Tue, 25 Apr 2006 00:16:12 GMT\r
    81: Server: felix web server\r
    82: Last-Modified: Wed, 01 Feb 2006 18:51:37 GMT\r
    83: Connection: close\r
    84: Content-Type: text/html\r
    85: \r
    86: """;
    87: 
    88: val gif_header = """HTTP/0.9 200 OK\r
    89: Date: Sun, 30 Apr 2006 07:14:50 GMT\r
    90: Server: felix web server\r
    91: Last-Modified: Sun, 28 Nov 2004 18:59:31 GMT\r
    92: Connection: close\r
    93: Content-Type: image/gif\r
    94: \r
    95: """;
    96: 
    97: val css_header = """HTTP/0.9 200 OK\r
    98: Date: Sun, 30 Apr 2006 07:14:50 GMT\r
    99: Server: felix web server\r
   100: Last-Modified: Sun, 28 Nov 2004 18:59:31 GMT\r
   101: Connection: close\r
   102: Content-Type: text/css\r
   103: \r
   104: """;
   105: 
   106: 
   107: val notfound_header = """HTTP/0.9 404 Not Found\r
   108: Date: Sun, 30 Apr 2006 07:14:50 GMT\r
   109: Server: felix web server\r
   110: Last-Modified: Sun, 28 Nov 2004 18:59:31 GMT\r
   111: Connection: close\r
   112: Content-Type: text/html\r
   113: \r
   114: PAGE NOT FOUND:
   115: """;
   116: 
   117: 
   118: proc substitute(s: string, a: char, b: char, res: &string)
   119: {
   120:   var s2: string;
   121:   var slen = len s;
   122:   var i: int;
   123: 
   124:   for_each{i=0;}{i<slen}{i++;}
   125:   {
   126:      if s.[i] == a then
   127:      { s2 += b; } else
   128:      { s2 += s.[i]; } endif;
   129: 
   130:   };
   131: 
   132:   *res = s2;
   133: }
   134: 
   135: proc serve_file(infname: string, s: socket_t)
   136: {
   137:   var fname: string;
   138: 
   139:   // if empty string, serve index.html
   140:   // not quite right - needs to handle directories too, so
   141:   // not only foo.com/ -> index.html, but foo.com/images/ -> images/index.html
   142:   if "" == infname then { fname = "index.html"; }else{ fname = infname;}endif;
   143: 
   144:   // set mime type depending on extension...
   145:   // serve a "not found page" for that case (check for recursion)
   146:   print "serve file: "; print fname; endl;
   147: 
   148:   // this isn't right, don't want the contents parsed as text, want them
   149:   // sent faithfully over the wire. of course doesn't work for jpegs and other
   150:   // binary formats.
   151: 
   152:   var suffix: string;
   153:   var dotpos = stl_rfind(fname, char ".");
   154:   // print "dotpos = "; print dotpos; endl;
   155:   if stl_npos != dotpos then { suffix = substr(fname, dotpos+1); }
   156:   else {} endif;
   157: 
   158:   print "suffix is "; print suffix; endl;
   159: 
   160: #if WIN32
   161:   var wname: string;
   162: 
   163:   // quick 'n' dirty unix -> dos style pathnames
   164:   substitute(fname, char '/', char '\\', &wname);
   165:   print "mapped "; print fname; print " -> "; print wname; endl;
   166:   // send header
   167:   // TransmitFile
   168:   var wf: WFILE <- OpenFile(wname);
   169: 
   170:   if wf == INVALID_HANDLE_VALUE then
   171:   {
   172:     print "BUGGER: OpenFile failed: "; print (GetLastError()); endl;
   173:   } else {
   174:     print "opened "; print wname; endl;
   175: 
   176:     // mime type mapping from suffix. make better here.
   177:     if("gif" == suffix) then { write_string(s, gif_header); }
   178:     elif("css" == suffix) then { write_string(s, css_header); }
   179:     else { write_string(s, html_header); } endif;
   180: 
   181:     print "Transmitting file!\n";
   182:     TransmitFile(s, wf);
   183: 
   184:     // send footer
   185:     CloseFile(wf);
   186:   } endif;
   187: #elif POSIX
   188:   // this fn sets the O_NONBLOCK flag which is completely unnecessary
   189:   // as read goes via the preading worker fifo. don't know if
   190:   // O_NONBLOCK even works on actual files.
   191:   var fd = Faio_posix::ropen(fname);
   192: 
   193:   if Faio_posix::invalid fd then
   194:   {
   195:     print "BUGGER, posix open failed\n";
   196:     write_string(s, notfound_header);
   197:     write_string(s, fname+"\r\n\n");
   198:   } else {
   199:     print "got fd="; print$ str fd; endl;
   200: 
   201:     // mime type mapping from suffix. make better here.
   202:     // factor out
   203:     if("gif" == suffix) then { write_string(s, gif_header); }
   204:     elif("css" == suffix) then { write_string(s, css_header); }
   205:     else { write_string(s, html_header); } endif;
   206: 
   207:     var from_strm: Faio_posix::fd_t = fd;
   208:     var to_strm: socket_t = s;
   209:     Flx_stream::cat(from_strm, to_strm);
   210: 
   211:     dbg q"close file $from_strm\n";
   212:     iclose(from_strm); // this'll know how to close a unix fd
   213:   } endif;
   214: 
   215:   // var contents = Text_file::load(fname);
   216:   // print "loaded: "; print contents; endl;
   217:   // print "contents len="; print (len contents); endl;
   218:   // write_string(s, html_header + contents);
   219: 
   220: #endif
   221: }
   222: 
   223: val webby_port = 1234;
   224: 
   225: print "FLX WEB!!! listening on port "; print webby_port; endl;
   226: 
   227: // up the queue len for stress testing
   228: var p = webby_port;
   229: var listener: socket_t;
   230: mk_listener(&listener, &p, 10);
   231: 
   232: proc forever {
   233:   var s: socket_t;
   234:   accept(listener, &s);  // blocking
   235:   dbg q"got connection $s\n";  // error check here
   236: 
   237:   // hmm - spawning an fthread is blocking the web server. don't know why
   238:   print "spawning fthread to handle connection\n";
   239:   spawn_fthread {
   240:     // should spawn fthread here to allow for more io overlap
   241: 
   242:     var line: string;
   243:     get_line(s, &line);  // should be the GET line.
   244: 
   245:     val poo =
   246:     if "GET " == line.[0 to 4] then line.[4 to ] else "" endif;
   247:     print ("poo="poo); endl;
   248: 
   249:   //print ("blah " line.[0 to 4]); endl;
   250:     print "got line: "; print line; endl;
   251: 
   252:     // now I need to parse the GET line, get a file name out of its url
   253:     // (e.g. unqualfied -> index.html and name/flx.jpg -> flx.jpg
   254:     var succ: bool;
   255:     var base: string;
   256:     var file: string;
   257: 
   258:     parse_get_line(line, succ, base, file);
   259:     // print "succ="; print succ; endl;
   260: 
   261:     if succ then {
   262:       print "well formed get...\n";
   263:       print "base="; print base; endl;
   264:       print "file="; print file; endl;
   265: 
   266:       serve_file(file, s);
   267:     } else {
   268:       print "BAD get line: "; print line; endl;
   269:     } endif;
   270: 
   271:     // we've only read the GET line, so let's flush out the rest of
   272:     // the http request so we don't get connection reset errors when
   273:     // we close the socket. shutting down stops cat blocking (?)
   274:     Faio_posix::shutdown(s, 1); // disallow further sends.
   275:     cat(s, DEVNULL);
   276: 
   277:     fprint$ cerr,q"closing socket $s\n";
   278:     ioclose(s);
   279: 
   280:   };
   281:   collect();
   282:   forever;
   283: };
   284: forever;
   285: 
   286: iclose(listener);
   287: 
End felix section to tools/webserver.flx[1]