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 = """
    80: HTTP/0.9 200 OK\r
    81: Date: Tue, 25 Apr 2006 00:16:12 GMT\r
    82: Server: felix web server\r
    83: Last-Modified: Wed, 01 Feb 2006 18:51:37 GMT\r
    84: Connection: close\r
    85: Content-Type: text/html\r
    86: \r
    87: """;
    88: 
    89: val gif_header = """
    90: HTTP/0.9 200 OK\r
    91: Date: Sun, 30 Apr 2006 07:14:50 GMT\r
    92: Server: felix web server\r
    93: Last-Modified: Sun, 28 Nov 2004 18:59:31 GMT\r
    94: Connection: close\r
    95: Content-Type: image/gif\r
    96: \r
    97: """;
    98: 
    99: val css_header = """
   100: HTTP/0.9 200 OK\r
   101: Date: Sun, 30 Apr 2006 07:14:50 GMT\r
   102: Server: felix web server\r
   103: Last-Modified: Sun, 28 Nov 2004 18:59:31 GMT\r
   104: Connection: close\r
   105: Content-Type: text/css\r
   106: \r
   107: """;
   108: 
   109: 
   110: val notfound_header = """
   111: HTTP/0.9 404 Not Found\r
   112: Date: Sun, 30 Apr 2006 07:14:50 GMT\r
   113: Server: felix web server\r
   114: Last-Modified: Sun, 28 Nov 2004 18:59:31 GMT\r
   115: Connection: close\r
   116: Content-Type: text/html\r
   117: \r
   118: PAGE NOT FOUND:
   119: """;
   120: 
   121: 
   122: proc substitute(s: string, a: char, b: char, res: &string)
   123: {
   124:   var s2: string;
   125:   var slen = len s;
   126:   var i: int;
   127: 
   128:   for_each{i=0;}{i<slen}{i++;}
   129:   {
   130:      if s.[i] == a then
   131:      { s2 += b; } else
   132:      { s2 += s.[i]; } endif;
   133: 
   134:   };
   135: 
   136:   *res = s2;
   137: }
   138: 
   139: proc serve_file(infname: string, s: socket_t)
   140: {
   141:   var fname: string;
   142: 
   143:   // if empty string, serve index.html
   144:   // not quite right - needs to handle directories too, so
   145:   // not only foo.com/ -> index.html, but foo.com/images/ -> images/index.html
   146:   if "" == infname then { fname = "index.html"; }else{ fname = infname;}endif;
   147: 
   148:   // set mime type depending on extension...
   149:   // serve a "not found page" for that case (check for recursion)
   150:   print "serve file: "; print fname; endl;
   151: 
   152:   // this isn't right, don't want the contents parsed as text, want them
   153:   // sent faithfully over the wire. of course doesn't work for jpegs and other
   154:   // binary formats.
   155: 
   156:   var suffix: string;
   157:   var dotpos = stl_rfind(fname, char ".");
   158:   // print "dotpos = "; print dotpos; endl;
   159:   if stl_npos != dotpos then { suffix = substr(fname, dotpos+1); }
   160:   else {} endif;
   161: 
   162:   print "suffix is "; print suffix; endl;
   163: 
   164: #if WIN32
   165:   var wname: string;
   166: 
   167:   // quick 'n' dirty unix -> dos style pathnames
   168:   substitute(fname, char '/', char '\\', &wname);
   169:   print "mapped "; print fname; print " -> "; print wname; endl;
   170:   // send header
   171:   // TransmitFile
   172:   var wf: WFILE <- OpenFile(wname);
   173: 
   174:   if wf == INVALID_HANDLE_VALUE then
   175:   {
   176:     print "BUGGER: OpenFile failed: "; print (GetLastError()); endl;
   177:   } else {
   178:     print "opened "; print wname; endl;
   179: 
   180:     // mime type mapping from suffix. make better here.
   181:     if("gif" == suffix) then { write_string(s, gif_header); }
   182:     elif("css" == suffix) then { write_string(s, css_header); }
   183:     else { write_string(s, html_header); } endif;
   184: 
   185:     print "Transmitting file!\n";
   186:     TransmitFile(s, wf);
   187: 
   188:     // send footer
   189:     CloseFile(wf);
   190:   } endif;
   191: #elif POSIX
   192:   // this fn sets the O_NONBLOCK flag which is completely unnecessary
   193:   // as read goes via the preading worker fifo. don't know if
   194:   // O_NONBLOCK even works on actual files.
   195:   var fd = Faio_posix::ropen(fname);
   196: 
   197:   if Faio_posix::invalid fd then
   198:   {
   199:     print "BUGGER, posix open failed\n";
   200:     write_string(s, notfound_header);
   201:     write_string(s, fname+"\r\n\n");
   202:   } else {
   203:     print "got fd="; print$ str fd; endl;
   204: 
   205:     // mime type mapping from suffix. make better here.
   206:     // factor out
   207:     if("gif" == suffix) then { write_string(s, gif_header); }
   208:     elif("css" == suffix) then { write_string(s, css_header); }
   209:     else { write_string(s, html_header); } endif;
   210: 
   211:     var from_strm: Faio_posix::fd_t = fd;
   212:     var to_strm: socket_t = s;
   213:     Flx_stream::cat(from_strm, to_strm);
   214: 
   215:     dbg q"close file $from_strm\n";
   216:     iclose(from_strm); // this'll know how to close a unix fd
   217:   } endif;
   218: 
   219:   // var contents = Text_file::load(fname);
   220:   // print "loaded: "; print contents; endl;
   221:   // print "contents len="; print (len contents); endl;
   222:   // write_string(s, html_header + contents);
   223: 
   224: #endif
   225: }
   226: 
   227: val webby_port = 1234;
   228: 
   229: print "FLX WEB!!! listening on port "; print webby_port; endl;
   230: 
   231: // up the queue len for stress testing
   232: var p = webby_port;
   233: var listener: socket_t;
   234: mk_listener(&listener, &p, 10);
   235: 
   236: proc forever {
   237:   var s: socket_t;
   238:   accept(listener, &s);  // blocking
   239:   dbg q"got connection $s\n";  // error check here
   240: 
   241:   // hmm - spawning an fthread is blocking the web server. don't know why
   242:   print "spawning fthread to handle connection\n";
   243:   spawn_fthread {
   244:     // should spawn fthread here to allow for more io overlap
   245: 
   246:     var line: string;
   247:     get_line(s, &line);  // should be the GET line.
   248: 
   249:     val poo =
   250:     if "GET " == line.[0 to 4] then line.[4 to ] else "" endif;
   251:     print ("poo="poo); endl;
   252: 
   253:   //print ("blah " line.[0 to 4]); endl;
   254:     print "got line: "; print line; endl;
   255: 
   256:     // now I need to parse the GET line, get a file name out of its url
   257:     // (e.g. unqualfied -> index.html and name/flx.jpg -> flx.jpg
   258:     var succ: bool;
   259:     var base: string;
   260:     var file: string;
   261: 
   262:     parse_get_line(line, succ, base, file);
   263:     // print "succ="; print succ; endl;
   264: 
   265:     if succ then {
   266:       print "well formed get...\n";
   267:       print "base="; print base; endl;
   268:       print "file="; print file; endl;
   269: 
   270:       serve_file(file, s);
   271:     } else {
   272:       print "BAD get line: "; print line; endl;
   273:     } endif;
   274: 
   275:     fprint$ cerr,q"closing socket $s\n";
   276:     ioclose(s);
   277: 
   278:   };
   279:   collect();
   280:   forever;
   281: };
   282: forever;
   283: 
   284: iclose(listener);
   285: 
End felix section to tools/webserver.flx[1]