Main Page | Class Hierarchy | Alphabetical List | Class List | File List | Class Members | File Members | Related Pages

HttpRequest.d

Go to the documentation of this file.
00001 /*******************************************************************************
00002 
00003         @file HttpRequest.d
00004         
00005         Copyright (C) 2004 Kris Bell
00006         
00007         This software is provided 'as-is', without any express or implied
00008         warranty. In no event will the authors be held liable for damages
00009         of any kind arising from the use of this software.
00010         
00011         Permission is hereby granted to anyone to use this software for any 
00012         purpose, including commercial applications, and to alter it and/or 
00013         redistribute it freely, subject to the following restrictions:
00014         
00015         1. The origin of this software must not be misrepresented; you must 
00016            not claim that you wrote the original software. If you use this 
00017            software in a product, an acknowledgment within documentation of 
00018            said product would be appreciated but is not required.
00019 
00020         2. Altered source versions must be plainly marked as such, and must 
00021            not be misrepresented as being the original software.
00022 
00023         3. This notice may not be removed or altered from any distribution
00024            of the source.
00025 
00026 
00027                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
00028 
00029         
00030         @version        Initial version, April 2004      
00031         @author         Kris
00032 
00033 
00034 *******************************************************************************/
00035 
00036 module mango.http.server.HttpRequest;
00037 
00038 private import  mango.utils.Text;
00039 
00040 private import  mango.io.Uri,
00041                 mango.io.Token,
00042                 mango.io.Buffer,
00043                 mango.io.Reader,
00044                 mango.io.Socket,
00045                 mango.io.Exception,
00046                 mango.io.Tokenizer;
00047 
00048 private import  mango.io.model.IBuffer,
00049                 mango.io.model.IWriter;
00050 
00051 private import  mango.http.HttpReader;
00052 
00053 private import  mango.http.server.HttpParams,
00054                 mango.http.server.HttpCookies,
00055                 mango.http.server.HttpHeaders,
00056                 mango.http.server.HttpMessage;
00057 
00058 private import  mango.http.server.model.IProviderBridge;
00059 
00060 /******************************************************************************
00061 
00062         Define an http request from a user-agent (client). Note that all
00063         data is managed on a thread-by-thread basis.
00064 
00065 ******************************************************************************/
00066 
00067 class HttpRequest : HttpMessage, IWritable
00068 {
00069         private int                     port;
00070         private char[]                  host;
00071         private bool                    mimed,
00072                                         uried,
00073                                         gulped;
00074 
00075         // these are per-thread instances also
00076         private MutableUri              uri;
00077         private CompositeToken          line;
00078         private HttpReader              reader;
00079         private HttpParams              params;
00080         private HttpCookies             cookies;
00081         private StartLine               startLine;
00082                 
00083         static private InvalidStateException InvalidState;
00084 
00085         /**********************************************************************
00086 
00087                 Setup exceptions and so on
00088 
00089         **********************************************************************/
00090 
00091         static this()
00092         {
00093                 InvalidState = new InvalidStateException("Invalid request state");
00094         }
00095 
00096         /**********************************************************************
00097 
00098                 Create a Request instance.  Note that we create a bunch of
00099                 internal support objects on a per-thread basis. This is so
00100                 we don't have to create them on demand; however, we should
00101                 be careful about resetting them all before each new usage.
00102 
00103         **********************************************************************/
00104 
00105         this (IProviderBridge bridge)
00106         {
00107                 IBuffer buffer;
00108 
00109                 super (bridge, null);
00110                 buffer = super.getBuffer();
00111                 
00112                 // workspace for parsing the request URI
00113                 uri = new MutableUri(); 
00114 
00115                 // HTTP request start-line (e.g. "GET / HTTP/1.1")      
00116                 startLine = new StartLine();
00117 
00118                 // input parameters, parsed from the query string
00119                 params = new HttpParams ();
00120 
00121                 // Convenience reader. Typically used for POST requests
00122                 reader = new HttpReader (buffer);
00123 
00124                 // Cookie parser. This is a wrapper around the Headers
00125                 cookies = new HttpCookies (getHeader());
00126 
00127                 // Token for initial parsing of input header lines
00128                 line = new CompositeToken (Tokenizers.line, buffer);
00129         }
00130 
00131         /**********************************************************************
00132 
00133                 Reset this request, ready for the next connection
00134 
00135         **********************************************************************/
00136 
00137         void reset()
00138         {
00139                 port = Uri.InvalidPort;
00140                 host = null;
00141                 uried = false;
00142                 mimed = false;
00143                 gulped = false;
00144 
00145                 uri.reset();
00146                 super.reset();
00147                 params.reset();
00148                 cookies.reset();
00149         }
00150         
00151         /**********************************************************************
00152 
00153                 Return the HTTP startline from the connection request
00154 
00155         **********************************************************************/
00156 
00157         StartLine getStartLine()
00158         {
00159                 return startLine;
00160         }
00161 
00162         /**********************************************************************
00163 
00164                 Return the request Uri as an immutable version ...
00165 
00166         **********************************************************************/
00167 
00168         Uri getRequestUri()
00169         {
00170                 if (! uried)
00171                    {
00172                    uri.parse (startLine.getPath());
00173                    if (uri.getScheme() is null)
00174                        uri.setScheme (getServerScheme());
00175                    uried = true;
00176                    }
00177                 return uri;
00178         }
00179 
00180         /**********************************************************************
00181 
00182                 Ensure the uri has a host present. Return as an immutable 
00183                 
00184         **********************************************************************/
00185 
00186         Uri getExplicitUri()
00187         {
00188                 getRequestUri();
00189 
00190                 if (uri.getHost is null)
00191                     uri.setHost (getHost);
00192                 return uri;
00193         }
00194 
00195         /**********************************************************************
00196 
00197                 Return the reader for the request input. This sets a 
00198                 boundary sentinel, indicating we're finished processing
00199                 the input headers.
00200 
00201         **********************************************************************/
00202 
00203         HttpReader getReader()
00204         {
00205                 // User is reading input. Cannot read headers anymore
00206                 gulped = true;
00207                 return reader;
00208         }
00209 
00210         /**********************************************************************
00211 
00212                 Return the set of parsed request cookies
00213 
00214         **********************************************************************/
00215 
00216         HttpCookies getInputCookies()
00217         {
00218                 if (gulped)
00219                     throw InvalidState;
00220                 return cookies;
00221         }
00222 
00223         /**********************************************************************
00224 
00225                 Return the set of parsed input headers
00226 
00227         **********************************************************************/
00228 
00229         HttpHeaders getInputHeaders()
00230         {
00231                 if (gulped)
00232                     throw InvalidState;
00233                 return getHeader();
00234         }
00235 
00236         /**********************************************************************
00237 
00238                 Return the set of input parameters, from the query string
00239                 and/or from POST data.
00240 
00241         **********************************************************************/
00242 
00243         HttpParams getInputParameters()
00244         {
00245                 // parse Query or Post parameters
00246                 if (! params.isParsed ())
00247                    {
00248                    char[] query = getRequestUri().getQuery();
00249 
00250                    // do we have a query string?
00251                    if (query.length)
00252                        // yep; parse that
00253                        params.parse (query);
00254                    else
00255                       // nope; do we have POST parameters?
00256                       if (startLine.getMethod() == "POST" && 
00257                           super.getContentType() == "application/x-www-form-urlencoded")
00258                          {
00259                          // yep; parse from input buffer
00260                          int length = getHeader.getInt (HttpHeader.ContentLength);
00261                          if (length < 0)
00262                              throw new IOException ("No params supplied for http POST");
00263                          else
00264                             if (length > HttpHeader.MaxPostParamSize)
00265                                 throw new IOException ("Post parameters exceed maximum length");
00266                             else
00267                                {
00268                                char[] c = cast(char[]) getBuffer.get (length);
00269                                if (c)
00270                                    params.parse (uri.decode (Text.replace(c, '+', ' ')));
00271                                else
00272                                   throw new IOException ("Post parameters exceed buffer size");
00273                                }
00274                          }
00275                    }
00276                 return params;
00277         }
00278 
00279         /**********************************************************************
00280 
00281                 Return the buffer attached to the input conduit. This
00282                 also sets a sentinel indicating we cannot read headers 
00283                 anymore.
00284                 
00285         **********************************************************************/
00286 
00287         IBuffer getInputBuffer()
00288         {
00289                 // User is reading input. Cannot read headers anymore
00290                 gulped = true;
00291                 return super.getBuffer();
00292         }
00293 
00294         /**********************************************************************
00295 
00296                 Write the startline and all input headers to the provider
00297                 IWriter. This can be used for debug purposes.
00298 
00299         **********************************************************************/
00300 
00301         void write (IWriter writer)
00302         {
00303                 startLine.write (writer);
00304                 super.write (writer);
00305         }
00306 
00307         /**********************************************************************
00308 
00309                 Parse all headers from the input.
00310 
00311         **********************************************************************/
00312 
00313         void readHeaders()
00314         {
00315                 IBuffer input = super.getBuffer();
00316 
00317                 // skip any blank lines
00318                 while (line.next() && line.getLength() == 0) 
00319                       {}
00320 
00321                 // is this a bogus request?
00322                 if (input.readable() == 0)
00323                     throw new IOException ("truncated request");
00324                     
00325                 // load HTTP request
00326                 startLine.parse (line.toString());
00327 
00328                 // populate headers
00329                 getHeader().parse (input);
00330                
00331                 version (ShowHeaders)
00332                         {        
00333                         Stdout.cr().put(">>>> request Headers:").cr();
00334                         getHeader().write (Stdout);
00335                         }
00336         }
00337 
00338         /**********************************************************************
00339 
00340                 Proxy this request across to the server instance 
00341 
00342         **********************************************************************/
00343 
00344         char[] getRemoteAddr()
00345         {
00346                 return getBridge().getServer().getRemoteAddress(getConduit());
00347         }
00348 
00349         /**********************************************************************
00350 
00351                 Proxy this request across to the server instance 
00352 
00353         **********************************************************************/
00354 
00355         char[] getRemoteHost()
00356         {
00357                 return getBridge().getServer().getRemoteHost(getConduit());
00358         }
00359 
00360         /**********************************************************************
00361 
00362                 Ask the server instance what protocol it is using
00363 
00364         **********************************************************************/
00365 
00366         char[] getServerScheme()
00367         {
00368                 return getBridge().getServer().getProtocol();
00369         }
00370 
00371         /**********************************************************************
00372 
00373                 Return the encoding from the input headers.
00374 
00375         **********************************************************************/
00376 
00377         char[] getEncoding()
00378         {
00379                 getMimeType();
00380                 return super.getEncoding();
00381         }
00382 
00383         /**********************************************************************
00384 
00385                 Return the mime-type from the input headers.
00386 
00387         **********************************************************************/
00388 
00389         char[] getMimeType()
00390         {
00391                 if (! mimed)
00392                    {
00393                    setMimeAndEncoding (super.getContentType());
00394                    mimed = true;
00395                    }
00396                 return super.getMimeType();
00397         }
00398 
00399         /**********************************************************************
00400 
00401                 Return the port number this request was sent to.
00402 
00403         **********************************************************************/
00404 
00405         int getPort()
00406         {
00407                 if (port == Uri.InvalidPort)
00408                    {
00409                    getHost();
00410                    if (port == Uri.InvalidPort)
00411                        // return port from server connection
00412                        port = getBridge().getServer().getPort();
00413                    }
00414                 return port;
00415         }
00416 
00417         /**********************************************************************
00418 
00419                 Get the host name. If we can't get it from the Uri, then
00420                 we try to extract for the host header. Failing that, we
00421                 ask the server instance to provide it for us.
00422 
00423         **********************************************************************/
00424 
00425         char[] getHost()
00426         {
00427                 // return previously determined host
00428                 if (host.length)
00429                     return host;
00430 
00431                 // return host from absolute uri (make sure we parse it first)
00432                 Uri uri = getRequestUri ();
00433 
00434                 host = uri.getHost ();
00435                 port = uri.getPort ();
00436                 if (host.length)
00437                     return host;
00438 
00439                 // return host from header field
00440                 host = Text.trim (getHeader().get(HttpHeader.Host));
00441                 port = Uri.InvalidPort;
00442 
00443                 if (host.length)
00444                    {
00445                    int colon = Text.indexOf (host, ':');
00446                    if (colon >= 0)
00447                       {
00448                       if (colon < host.length)
00449                           try {
00450                               port = Text.atoi (host[colon+1..host.length]);
00451                               } catch (Exception e){}
00452                       host = host[0..colon];
00453                       }
00454                     return host;
00455                     }
00456 
00457                 // return host from server connection
00458                 host = getBridge().getServer().getHost();
00459                 return host;
00460         }
00461 }
00462 
00463 
00464 /******************************************************************************
00465 
00466         Class to represent an HTTP start-line
00467 
00468 ******************************************************************************/
00469 
00470 private class StartLine : IWritable
00471 {
00472 //version = UseTokenizer;
00473 
00474 version (UseTokenizer)
00475 {
00476         Buffer          buf;
00477         BoundToken      path,
00478                         method,
00479                         protocol;
00480 
00481         /**********************************************************************
00482 
00483         **********************************************************************/
00484 
00485         this()
00486         {
00487                 buf = new Buffer;
00488 
00489                 // bind tokens to a space-tokenizer
00490                 path = new BoundToken (Tokenizers.space);
00491                 method = new BoundToken (Tokenizers.space);
00492                 protocol = new BoundToken (Tokenizers.space);
00493         }
00494 
00495         /**********************************************************************
00496 
00497         **********************************************************************/
00498 
00499         void parse (char[] line)
00500         {
00501                 // setup our buffer with the request
00502                 buf.setValidContent(line);
00503 
00504                 // extract exactly 3 tokens
00505                 if ((method.next(buf) && path.next(buf)) ^ protocol.next(buf))
00506                    {}
00507                 else
00508                    throw new IOException ("Invalid HTTP start-line: '"~line~"'");
00509         }
00510 
00511         /**********************************************************************
00512 
00513         **********************************************************************/
00514 
00515         char[] getMethod()
00516         {
00517                 return method.toString();
00518         }
00519 
00520         /**********************************************************************
00521 
00522         **********************************************************************/
00523 
00524         char[] getPath()
00525         {
00526                 return path.toString();
00527         }
00528 
00529         /**********************************************************************
00530 
00531         **********************************************************************/
00532 
00533         char[] getProtocol()
00534         {
00535                 return protocol.toString();
00536         }
00537 
00538         /**********************************************************************
00539 
00540         **********************************************************************/
00541 
00542         char[] toString()
00543         {
00544                 return getMethod()~" "~getPath()~" "~getProtocol();
00545         }
00546 
00547         /**********************************************************************
00548 
00549         **********************************************************************/
00550 
00551         void write (IWriter writer)
00552         {
00553                writer.put(toString()).cr();
00554         }
00555 }
00556 else
00557 {
00558         private import mango.http.utils.TokenStack;
00559 
00560         Token[] tokens;
00561 
00562         /**********************************************************************
00563 
00564                 Construct a startline with a set of tokens.
00565 
00566         **********************************************************************/
00567 
00568         this()
00569         {
00570                 tokens = new Token[0];
00571                 TokenStack.resize (tokens, 3);
00572         }
00573 
00574         /**********************************************************************
00575 
00576                 Parse the start line into its constituent components.
00577 
00578         **********************************************************************/
00579 
00580         void parse (char[] line)
00581         {
00582                 // parse http request line
00583                 int index;
00584                 int anchor = 0;
00585                 int count = 0;
00586                 
00587                 do {
00588                    index = Text.indexOf (line, ' ', anchor);
00589                    if (index == -1)
00590                        index = line.length;
00591                    tokens[count].set(line[anchor..index]);
00592                    anchor = index + 1;
00593                    } while (++count < 3 && anchor < line.length);
00594 
00595                 if (count != 3 || anchor < line.length)
00596                     throw new IOException ("Invalid HTTP start-line: '"~line~"'");
00597         }
00598 
00599         /**********************************************************************
00600 
00601                 Return the request method
00602 
00603         **********************************************************************/
00604 
00605         char[] getMethod()
00606         {
00607                 return tokens[0].toString();
00608         }
00609 
00610         /**********************************************************************
00611 
00612                 Return the request path
00613 
00614         **********************************************************************/
00615 
00616         char[] getPath()
00617         {
00618                 return tokens[1].toString();
00619         }
00620 
00621         /**********************************************************************
00622 
00623                 Return the request protocol
00624 
00625         **********************************************************************/
00626 
00627         char[] getProtocol()
00628         {
00629                 return tokens[2].toString();
00630         }
00631 
00632         /**********************************************************************
00633 
00634                 Convert back to a text string
00635 
00636         **********************************************************************/
00637 
00638         char[] toString()
00639         {
00640                 return getMethod()~" "~getPath()~" "~getProtocol();
00641         }
00642 
00643         /**********************************************************************
00644                 
00645                 Output startline to the specified IWriter
00646 
00647         **********************************************************************/
00648 
00649         void write (IWriter writer)
00650         {
00651                writer.put (toString).cr();
00652         }
00653 }
00654 }
00655 

Generated on Sun Nov 7 19:06:51 2004 for Mango by doxygen 1.3.6