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

Generated on Fri May 27 18:11:56 2005 for Mango by  doxygen 1.4.0