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

Generated on Tue Jan 25 21:18:22 2005 for Mango by doxygen 1.3.6