KomHttpServer

ComancheModule
Comanche modules response to the message #processHttp and are responsible for examining the http serving environment and possibly altering that environment. They can act a filters on both the inbound request and the outbound response. They may also produce content (and even filter content). Modules for any given server are arranged in a hierarchy that is formed by following the path of subModules. Any given module may or may not process its subModules. The method #processHttp answers a boolean to indicate whether or not a response has been made available. It is up to the parent module to decide whether or not to continue processing other modules if a response is available. The default behavior is to stop processing and return once a response is made available.
addSubModule:
attachAllLoggersToTheTranscript
detachAllLoggersFromTranscripts
errorResponseStatus:description:
isComancheModule
isCore
isDir
isDoc
isLog
isSession
isVhost
loggers
modulesOnStack
new
openTranscriptsOnAllLoggers
options
processHttp
<Boolean> Subclasses should override this method to process
the current http request (accessed via HttpRequest current).
Answer true if a response has been made available (via
HttpResponse current:) and false if no response was generated.
processHttpRequest:
<HttpResponse>
processSubModules
processVisitor:
subModules
subModulesDo:
validate
validateContext:
Validation is successful by default, subclass to check the
context stack for the the presence of required modules and add
to the list of problems
validateForStack:
validateForStack:problems:
Subclasses may wish to override this method to check that
prerequisite modules are on the stack above them
version
visitModules:
visitWithValuable:
HttpAdaptor
kom/4.12 (Comanche/4.12)
HTTPAdaptor (kom/4.8; bolot 4/2/2001 18:48)
- logging is removed from kom46 core
- so is Process>>contextAt:ifAbsent:
- kom48 adds rudimentary persistent connections
-- see keepAliveConnection senders (also in Preferences)
Comment from kom46:
I encapsulate the HTTP protocol from my clients (who must support the HttpPlug interface). I use an HttpRequest to pick apart a request and I use an HttpResponse to formulate a response. Mainly, I broker the conversation, turning a request into a message send (to an HttpPlug) and converting the answer to an HttpResponse. Thus, my clients must simply implement a message based protocol (eliminating the need for them to be concerned with HTTP syntax).
Instance Variables:
stream <Stream> - A bidirectional stream for reading and writing request (note: previously Comanche separated the readStream and writeStream, if you need to separate the read stream from the write stream you can create a new bidirectional stream class that uses two separate streams for reading and writing)
httpService <HttpService> - an instance of an HttpService or a protocol compatible substitute; this object is used as the error handler and the target of http request dispatching
postProcessors <OrderedCollection | nil> - this is a list of objects that are sent post processing messages (after the response has been written); this enables objects to request that they get called after the HttpAdaptor has actually written the response onto the write stream
addConnectionHeaders:request:count:
addPostProcessor:
Add anObject to my list of postProcessor, anObject must respond to
the message #doPostProcessingFor:response:
beginConversation
note: there is a lot of effort in this method to ensure that we don't have
and explicit method returns. For some reason (bugs in exception handling?) we
can get errors if we have explicit returns...this behavior seems to
only happen when Seaside is also loaded (though it doesn't look like Seaside code
is being invoked when the problems happen.
buildTimeStamp:
current
current:
dataTimeout
dataTimeout:
destroy
dispatchRequest:
<HttpResponse> Dispatches an HttpRequest as a method to the target and
answers an HttpResponse to be transferred to the client
doPostProcessingFor:response:
Do the post processing for the given request/response pair and
then reset our list of post processors.
errorHandler
httpService
httpService:
initializeOn:httpService:
keepAlive
keepAlive:
maxKeepAliveRequests
maxKeepAliveRequests:
notFoundResponseFor:
postProcessorsDo:
readAndWriteTo:service:
readAndWriteToSocket:service:
readRequest
readStream
serverType
socketStream
stream
target
timeStamp
timezoneOffset
timezoneOffset:
writeResponse:for:
<Boolean> Answer whether or not we succeeded in writing out the
response. If we did not, the caller may want to shut down this Http
conversation. We try to handle errors in writing the response by
getting and writing another response (note: this might not always work
since the error could have occurred after some data has been written
on the response stream). For debugging, the method #handleResponseWritingError:
can #pass on the exception to allow a walkback to appear.
writeStream
HttpFormDictionary
kom/4.10 (Comanche beta 4.10)
HttpFormDictionary
bolot 6/22/2001 12:09
- use this instead of plain Dictionary for GET and POST forms
- maintains multiple values for the same field
-- but is backwards compatible, #at: returns the first value (?)
-- so do #booleanAt:, #numberAt:
- to access the actual value at key, use #rawAt:
-- returns an OrderedCollection
TODO:
- file upload support
-- idea: first value is file name, second is FileStream?
Koubo 3/19/2002 12:25
fixed #at: and #at:ifAbsent: returns a String when the value had only one item. however, they returns a copied Collection of the value when it had multiple items.
at:
Answer the value associated with the key.
at:ifAbsent:
Answer the value associated with the key or, if key isn't found,
answer the result of evaluating aBlock.
at:put:
Set the value at key to be anObject. If key is not found, create a
new entry for key and set is value to anObject. Answer anObject.
booleanAt:
booleansAt:
numberAt:
numbersAt:
printElementsOn:
The original code used #skip:, but some streams do not support that,
and we don't really need it.
rawAt:
rawAt:ifAbsent:
stringAt:
stringsAt:
HttpPartialResponse
kom/4.12 (Comanche/4.12)
HttpPartialResponse (bolot 4/2/2001 18:49)
- not a very accurate name
- a subclass of HttpResponse
-- allows for long-execution modules to write directly to the client
producerBlock:
pvtWriteContentLengthOn:
do nothing, since the length is not known yet
pvtWriteContentsOn:
HttpRequest
kom/4.12 (Comanche/4.12)
bolot 2/20/2002 13:53
- rawUrl = the entire request string
- url = rawUrl up to ?
- queryString = rawUrl after the first ?
- rawUrl == url?queryString
HttpRequest (bolot 4/2/2001 18:51)
- HTTP request object wrapper
- handles details of HTTP
-- headers, formats, etc.
- as of kom47, handles multipart posts
- in kom49 (or kom50) a minor refactoring will happen
addKey:value:toForm:multipleValues:
clearPassword
contentLength
contentType
cookies
answer a dictionary with the cookies in the request
current
current:
decodeUrlEncodedForm:
decodeUrlEncodedForm:multipleValues:
defaultContentType
defaultMethod
defaultProtocol
defaultUrl
destroy
endOfRequestHeaderMarker
fields
Answer the fields (post or get) for the given request
firstLineOn:
getFields
retrieve fields encoded in the URL: http://aha/ha?a=1&b=2
getUsername
hashPassword:
header
header:
headerAt:
headerAt:ifAbsent:
host
initStatusString:
initialize
Subclasses should redefine this method to perform initializations on instance creation
initializeFromStream:
isDeleteRequest
isGetRequest
isHeaderRequest
isPersistent
isPostMultipart
Is this request a POST with multipart form data?
isPostRequest
isPutRequest
isUsername:password:
localAddress
Answer the address of the request originator
localPort
Answer the address of the request originator
method
method:
multiValueFormFields
multiValueFormFieldsDisable
disable use of multi-value form fields
multiValueFormFieldsEnable
enable use of multi-value form fields
multipartBoundary
boundary specification in the HTTP header looks like:
Content-Type: multipart/form-data; boundary=BOUNDARY
multipartFormFieldsDo:
USAGE:
request multipartFormFieldsDo:
[:chunk |
chunk saveToStream: aStream].
networkHost
new
nextChunkHeader
Read the next multipart data chunk's header
parseCookies:
PRIVATE: Parse a string in the format:
Cookie: NAME1=OPAQUE_STRING1; NAME2=OPAQUE_STRING2 ...
parseHttpHeader:
pathParts
postFields
Answer the multipart fields for a post request (if in fact this is a
POST request
printOn:
Append to the argument, aStream, a sequence of characters that
identifies the receiver.
propertyAt:
propertyAt:ifAbsent:
propertyAt:ifAbsentPut:
propertyAt:ifPresent:
propertyAt:put:
protocol
protocol:
queryString
queryString:
rawPostFields
save the POST form fields as is, for future processing, see #postFields
rawUrl
readFromStream:
readRequestHeaderFrom:
readStatusStringFrom:
RFC 2068 says in section 4.1 (Message Types) that 'In the interest of
robustness, servers SHOULD ignore any empty line(s) received where
a Request-Line is expected', so we ignore any leading CR/LF's
referer
remoteAddress
remoteAddress:
removePropertyAt:
removePropertyAt:ifAbsent:
responseCookies
cookies that need to be set
secretWord
session
setCookieName:value:expiresDate:expiresTime:path:domain:secure:
set a cookie in the Response
setCookieName:value:path:
set a cookie in the Response
setUsername:
save the username in a cookie
setUsername:password:
save the username/password in cookies
stream
timestamp
timestamp:
url
url:
user
currently, only basic authentication is supported
userObject:
writeOn:
this can be used to form a client-side request and send it over network
HttpResponse
kom/4.12 (Comanche/4.12)
bolot 2/20/2002 13:55
- cookies support
- defaultChunkSize delegates to Kom5Preferences
HttpResponse (bolot 4/2/2001 18:52)
Comment from kom46:
I am a response to an HttpRequest. I can formulate an HTTP response and send it out over a socket. An HttpAdapter will accept an HttpRequest, dispatch a method call to an HttpPlug (which will result in a stream or an error), and then formulat an instance of me to deliver the response to the client.
addCookies:
asHttpPartialResponseBlock:
asHttpResponseTo:
contentLength
contentType
contentType:
contents
contents:
cookies
current
current:
defaultContentType
destroy
fieldAt:
fieldAt:ifAbsent:
fieldAt:ifAbsentPut:
fieldAt:put:
fields
fromFileStream:
fromMIMEDocument:
fromStream:
fromStream:contentType:
fromString:
fromString:contentType:
fromUrl:
hashPassword:
httpVersion
initialize
Subclasses should redefine this method to perform initializations on instance creation
initializeStatusCodes
isPersistent
new
pvtWriteContentLengthOn:
pvtWriteContentTypeOn:
pvtWriteContentsOn:
pvtWriteCookiesOn:
pvtWriteFieldsOn:
pvtWriteStatusOn:
redirectTo:
responseChunkSize
responseChunkSize:
secretWord
setCookieName:value:expiresDate:expiresTime:path:domain:secure:
set a cookie in the Response
setCookieName:value:path:
set a cookie in the Response
setUsername:
save the username in a cookie
setUsername:password:
save the username/password in cookies
status
status:
status:contents:
statusCode
statusCodeAndReason
statusCodeFor:
statusDescriptionFor:
statusSymbolFor:
writeHeadersOn:
Header
writeOn:
HttpService
I am a comanche service that listens for inbound HTTP connectinos on a given port.
Usage:
Subclasses should override the #processHttpRequest: method to process incoming HTTP requests (an HttpRequest is passed as the sole argument to this method). The #processHttpRequest: method should always answer an instance of an HttpResponse. Starting and stopping instances of this class will start and stop listening on the given port number. You may also use instances of this class in a pluggable manner rather than subclassing (see examples below).
Instance Variables:
plug - An object that responds to the message #threadSafeValue: (typically a BlockContext or a MessageSend). If this variable is not nil, then the default implementation of #processHttpRequest: will send #threadSafeValue: to this object and answer the result. This enables ComancheHttpService to be used in a pluggable manner.
Pluggable Examples (MessageSend):
(HttpService on: 8080 named: 'Example Http Service')
onRequestDispatch: #processRequest: to: SomeGlobal;
start
Pluggable Examples (BlockContext):
(HttpService on: 8080 named: 'Example Http Service')
onRequestDo: [ :httpRequest | SomeGlobal processRequest: httpRequest ];
start
current
current:
detailedErrorResponseStatus:exception:
errorResponseStatus:description:
handleDispatchErrorsIn:
Note: We could break the error handling out into a separate
object to allow more sophisticated error handling scenarios, however
you can achieve the same result by writing a module that traps
errors and provides special handling. So, here, we just provide three
simple and common possibilities for handling dispatch errors.
handleResponseWritingError:
Handle errors that occur when trying to write out a response.
initialize
Subclasses should redefine this method to perform initializations on instance creation
initializeServerType
isDebugMode
isDeploymentMode
isVerboseMode
keepAlive
keepAlive:
mode
<#deployment | #debug | #verbose > - Answers the debugging
mode of the receiver.
mode:
aSymbol <#deployment | #debug | #verbose > - Sets the debugging
mode of the receiver.
module:
This message is useful if you wish to
validate your module.
onRequestDispatch:to:
onRequestDo:
perceivedHostName
Answer the host name as perceived by the client. If there
is no current HttpRequest, fall back on the name as determined
on the server.
perceivedPortNumber
Answer the port number as it is perceived by the client (which could
be different from the port we are listening on if for example you are
using port forwarding rules to reach your server). If there is no
current http request, fall back on the real port number that we are
listening on.
platform
plug
plug:
prepareForStartup
The squeak system just started up
processHttpRequest:
Subclasses can override this method and answer an instance of an
HttpResponse. Alternatively, if we have a plug (typically a BlockContext
or a MessageSend), then invoke it.
serve:
Subclasses should override this method to provide socket based
communications services. Each new socket connection is handed to
this method.
serverDescription
serverDescriptionOn:
serverHostName
Cache the host name (it can take several milliseconds to make this call
on some platforms. The cache value will be reset every time the image
is started
serverType
serviceName
setDebugMode
setDeploymentMode
setVerboseMode
start
version
KomAuthDb
I am a very basic authentication database. My passwords instvar is a dictionary whose keys are user names and values are hexadecimal encodings of passwords. Subclasses could override the encode: and decode:using: methods to provide a better "encryption" of the passwords. That wasn't done in this class because it would add a dependency on the cryptography package. I am designed to be used with ModAuth, which sends us the message #verifyPassword:forUser: to validate user ids and passwords.
addUser:withPassword:
decode:using:
encode:
includesUser:
initialize
Subclasses should redefine this method to perform initializations on instance creation
new
passwords
removeUser:
verifyPassword:forUser:
KomLogger
I am a very simple http logging facility. I implement a writable stream interface and expect to recieve arrays containing an HttpRequest (first element) and an HttpResponse (second element). I then translate the request and response pair into a textual log format and write that onto each stream in my streams instance variable. Other loggers may choose to write different log formats, or they could simply store the HttpRequest and HttpResponse objects (in an object DB for example). For convenience, a transcript window can be opened on a logger using #openTranscript. You Can also use the method #attachTheTranscript to copy the log entries onto the system Transcript. Use #detachTranscripts to remove all instances of TranscriptStream from the set of streams.
Currently I only support the Commog Log Format (CLF), which many log analysis tools can interpret. For more information on this format, see:
http://www.w3.org/Daemon/User/Config/Logging.html#common-logfile-format
For examples, see the class comments for ModLog.
Enhancement ideas:
- support a flexible logging format that uses some type of template string (ala Apache's logging facility) for producing each line in the log file
- implement a "multi-streamer" that implements a writable stream interface and will copy the output to multiple output streams
- implement a unix like tail tool that implement a writable stream interface and will "follow" everything that's written to it (similar to the Transcript, but designed to only show the most recent N number of lines or characters)
addStream:
Add a new output stream to the logger
attachTheTranscript
attachTranscript
detachTheTranscript
detachTranscripts
logEntryFor:response:
This is the Common Log Format (CLF)
next
Answer the next object accessible by the receiver.
nextPut:
Insert the argument, anObject, as the next object accessible by the
receiver. Answer anObject.
on:
openTranscript
removeStream:
Remove an output stream to the logger
streams
streams:
KomModuleValidator
A KomModuleValidator is xxxxxxxxx.
Instance Variables
problems: <Object>
problems
- xxxxx
doComancheModule:
This method assumes that module traversal happens in depth first order
problems
KomModuleVisitor
A KomModuleVisitor is xxxxxxxxx.
Instance Variables
doBlock:
doComancheModule:
For visiting a module hierarchy
doDyadicValuable:
By default, do nothing since these aren't really modules
doMessageSend:
doMonadicValuable:
By default, do nothing since these aren't really modules
KomSession
I am a session and am used by ModSession to associate sessions with incoming http requests. After a period of inactivity, sessions are expired.
Notes: This class is a hold over from the old HttpSession class. This class was renamed because it is not part of the HTTP protocol. See ModSession for details on how to establish sessions. You may use the attributes instance variable to hold additional state (such as user object for your application). Alternatively, other modules may use the session to assign additional dynamic bindings.
For examples see the class comments for ModSession.
attributes
answer the attributes stored in Session
current
current:
id
initialize
Subclasses should redefine this method to perform initializations on instance creation
lastAccess
lastAccess:
new
nextSessionID
printOn:
Append to the argument, aStream, a sequence of characters that
identifies the receiver.
touch
KomTracer
I am a KomLogger, but instead of writing a log file format, I write out a detailed trace of the http request and response pairs. The following example will dump a trace on the transcript.
| ma |
ma _ ModuleAssembly core.
ma logTo: (KomTracer on: Transcript).
ma addPlug: [ :request | HttpResponse fromString: 'Hello World!'].
(HttpService startOn: 8080 named: 'Example') plug: ma rootModule
logEntryFor:response:
This is the Common Log Format (CLF) - the stream is a TranscriptStream
ModAlias
This module will attempt to match a prefix to the URL of a request. If a match is made, its subModules will be processed. If not, then its subModules are not processed. The following is an example:
| ma |
ma _ ModuleAssembly core.
ma alias: '/hello' to:
[ma addPlug:
[ :request |
HttpResponse fromString: 'This is the aliased content']].
ma addPlug:
[ :request |
HttpResponse fromString: 'This is the default content'].
(HttpService startOn: 8080 named: 'Example') plug: ma rootModule
In this example, the urls http://localhost:8080/hello and http://localhost:8080/hello/some/more/path will show the aliased content while any url where the path does not begin with '/hello' will show the default content.
pathPrefix
pathPrefix:
processHttp
<Boolean> Subclasses should override this method to process
the current http request (accessed via HttpRequest current).
Answer true if a response has been made available (via
HttpResponse current:) and false if no response was generated.
ModAuth
This module, when present, requires that a request has user authentication info. It does not authenticate the supplied user id and password. I send the message #verifyPassword:forUser: to my authDb instance variable to validate user ids and passwords. You may supply your own user management object. A very simple authentication database is provided by the KomAuthDb class. Currently, this module only supports Basic authentication.
Here is an example of a simple configuration that authentications users:
| ma authdb |
authdb := KomAuthDb new.
authdb addUser: 'admin' withPassword: 'password'.
ma := ModuleAssembly core.
ma authenticate: authdb realm: 'Demo'.
ma addPlug:
[ :request |
HttpResponse fromString: 'Hello world!'].
(HttpService startOn: 8080 named: 'Example') plug: ma rootModule.
Future enhancement ideas:
- add support for more types of authentication
authDb
authDb:
authorizationResponse
decodeAuthorization:
extractAuthInfo:andDo:
extractAuthorization:
extractPassword:
extractUser:
processHttp
<Boolean> Subclasses should override this method to process
the current http request (accessed via HttpRequest current).
Answer true if a response has been made available (via
HttpResponse current:) and false if no response was generated.
realm
realm:
user
user:
verifyPassword:forUser:
ModAutoIndex
This module provides automatically generated listings of directory contents.
Required Bindings:
HttpRequest current
ModDir serverDirectory
Exported Bindings:
<none>
The following is an example:
| ma |
ma := ModuleAssembly core.
ma documentRoot: FileDirectory default fullName.
ma directoryIndex: 'index.html index.htm'.
ma serveFiles.
(HttpService startOn: 8080 named: 'Example') plug: ma rootModule
This example just starts a file server on your default directory. Any directories (that don't have an index file) will be rendered as listings by ModAutoIndex.
dirEntries:request:on:padNamesTo:padSizesTo:
directoryListing:directory:
entry:on:padNamesTo:padSizesTo:
parentEntry:on:
processHttp
<Boolean> Subclasses should override this method to process
the current http request (accessed via HttpRequest current).
Answer true if a response has been made available (via
HttpResponse current:) and false if no response was generated.
splitPath:to:
validateForStack:problems:
Subclasses may wish to override this method to check that
prerequisite modules are on the stack above them
ModCore
The is the Core module. It extracts a few things from a request and can establish a server root directory (which is not required). It will also process TRACE and OPTIONS requests.
host
host:
isCore
method
method:
options
options:
optionsResponse
path
path:
processHttp
<Boolean> Subclasses should override this method to process
the current http request (accessed via HttpRequest current).
Answer true if a response has been made available (via
HttpResponse current:) and false if no response was generated.
serverRoot
serverRoot:
traceResponse:
ModDir
This module provides basic directory handling (including redirects to handle the trailing slash problem).
Required Bindings:
HttpRequest current
ModDoc fullFilePath
Exported Bindings:
ModDir serverDirectory
The following is an example:
| ma |
ma := ModuleAssembly core.
ma documentRoot: FileDirectory default fullName.
ma directoryIndex: 'index.html index.htm'.
ma serveFiles.
(HttpService startOn: 8080 named: 'Example') plug: ma rootModule
This example just starts a file server on your default directory. Any directories (that don't have an index file) will be rendered as listings by ModAutoIndex.
directoryExists:
directoryIndex
directoryIndex:
directoryIndexExists:
fullFilePath
isDir
processHttp
<Boolean> Subclasses should override this method to process
the current http request (accessed via HttpRequest current).
Answer true if a response has been made available (via
HttpResponse current:) and false if no response was generated.
redirectWithTrailingSlash:
serverDirectory
serverDirectory:
validateForStack:problems:
Subclasses may wish to override this method to check that
prerequisite modules are on the stack above them
ModDoc
The core module for handling traditional web server duties
Required Bindings:
ModCore path
Exported Bindings:
ModDoc documentRoot
ModDoc relativeFilePath
The following is an example:
| ma |
ma := ModuleAssembly core.
ma documentRoot: FileDirectory default fullName.
ma directoryIndex: 'index.html index.htm'.
ma serveFiles.
(HttpService startOn: 8080 named: 'Example') plug: ma rootModule
This example just starts a file server on your default directory. Any directories (that don't have an index file) will be rendered as listings by ModAutoIndex.
documentRoot
documentRoot:
fullFilePath
initialize
Subclasses should redefine this method to perform initializations on instance creation
isDoc
notFoundResponseFor:
printOn:
Append to the argument, aStream, a sequence of characters that
identifies the receiver.
processHttp
Examine the path from ModCore and set the bindings for
document root and relative file path
relativeFilePath
relativeFilePath:
relativeFilePathFrom:
startUp
validateForStack:problems:
Subclasses may wish to override this method to check that
prerequisite modules are on the stack above them
ModFile
This module serves a file based on the setting of ModCore fullFilePath.
Required Bindings:
ModDoc documentRoot
ModDoc relativeFilePath
Exported Bindings:
<none>
The following is an example:
| ma |
ma := ModuleAssembly core.
ma documentRoot: FileDirectory default fullName.
ma directoryIndex: 'index.html index.htm'.
ma serveFiles.
(HttpService startOn: 8080 named: 'Example') plug: ma rootModule
This example just starts a file server on your default directory. Any directories (that don't have an index file) will be rendered as listings by ModAutoIndex.
processHttp
This method uses StandardFileStream explicitly instead
of relying on FileStream class>>concreteStream, since in this
case we just need to return the file 'as is' binary.
validateForStack:problems:
Subclasses may wish to override this method to check that
prerequisite modules are on the stack above them
ModLog
Instance Variable:
logger <Stream> - a stream for writing out request/response pairs
Examples:
The following example will log requests to the Transcript:
| ma |
ma _ ModuleAssembly core.
ma logTo: (KomLogger on: Transcript).
ma addPlug: [ :request | HttpResponse fromString: 'Hello World!'].
(HttpService startOn: 8080 named: 'Example') plug: ma rootModule
It is possible to segregate logging by inserting #logTo: messages a different places in the module assembly...one example using ModAlias follows:
| ma |
Smalltalk at: #MyHttpLog put: (WriteStream on: '').
ma _ ModuleAssembly core.
ma alias: '/log1' to: [ma logTo: (KomLogger on: Transcript)].
ma alias: '/log2' to: [ma logTo: (KomLogger on: (Smalltalk at: #MyHttpLog))].
ma addPlug: [ :request | HttpResponse fromString: 'Hello World!'].
(HttpService startOn: 8080 named: 'Example') plug: ma rootModule
In this example, all requests will respond with 'Hello World!', but requests whose url begins with '/log1' will be logged to the transcript and requests whose url begins with '/log2' will be logged to MyHttpLog. Other requests will not be logged.
In the next example, we illustrate how to start enable logging, and also how to open a transcript window on that log. Output sent to the logger will be concurrently written to the WriteStream "MyHttpLog" and to the TranscriptStream opened by the #openTranscript message.
| ma logger |
Smalltalk at: #MyHttpLog put: (WriteStream on: '').
ma _ ModuleAssembly core.
ma logTo: (logger := KomLogger on: (Smalltalk at: #MyHttpLog)).
ma addPlug: [ :request | HttpResponse fromString: 'Hello World!'].
(HttpService startOn: 8080 named: 'Example') plug: ma rootModule.
logger openTranscript.
Tip: With the SharedStreams package (on SqueakMap), a number of asynchronous logging configurations can be constructed.
doPostProcessingFor:response:
This method is called after the response has been written, we need
to log the request/response pair
isLog
logger
logger:
processHttp
<Boolean> Subclasses should override this method to process
the current http request (accessed via HttpRequest current).
Answer true if a response has been made available (via
HttpResponse current:) and false if no response was generated.
ModMulti
This module simply processes its subModules. It is handy when you'd like to wrap some modules together a work with them as a unit.
Example:
In the following example, a ModuleAssembly instantiated with #new will have a ModMulti instance as the root of the module stack. Adding further modules to the assembly results in those modules being added to the ModMulti instance. In this example, we create an assembly that could be used as the standard document server in a number of places in our main module assembly. In this manner, we can reuse module assemblies. Changes to our standard document server assembly would affect the behavior every where we refer to that assembly (also, direct manipulation of the module instances after construction will affect the behavior everywhere an assembly is refered...this may even eliminate the need to rebuild your module assembly).
| stdDocs ma |
"Create a stdDocs assembly"
stdDocs := ModuleAssembly new.
stdDocs directoryIndex: 'index.html index.htm'.
stdDocs serveFiles.
stdDocs notFound.
"Now create our main assembly"
ma := ModuleAssembly core.
ma alias: '/one' to:
[ma documentRoot: (FileDirectory default directoryNamed: 'one').
ma addAssembly: stdDocs].
ma alias: '/two' to:
[ma documentRoot: (FileDirectory default directoryNamed: 'two').
ma addAssembly: stdDocs].
ma addPlug:
[:request | HttpResponse fromString:
'<a href="one">go to one</a> <a href="two">go to two</a>'].
(HttpService startOn: 8080 named: 'Example') module: ma rootModule.
processHttp
<Boolean> Subclasses should override this method to process
the current http request (accessed via HttpRequest current).
Answer true if a response has been made available (via
HttpResponse current:) and false if no response was generated.
ModNotFound
I am a simple module that if reached will generate a not found response. This is useful if you do not wish to continue processing modules add some point in your module assembly.
The following is a simple example using the #notFound message of ModuleAssembly:
| ma |
ma := ModuleAssembly core.
ma alias: '/one' to:
[ma documentRoot: (FileDirectory default directoryNamed: 'one').
ma directoryIndex: 'index.html index.htm'.
ma serveFiles.
ma notFound].
ma addPlug:
[:request | HttpResponse fromString:
('<a href="one">go to one</a> If the path begins with',
' "/one" we should never end up here')].
(HttpService startOn: 8080 named: 'Example') module: ma rootModule.
By contrast, the following example will end up in our default response if no matching file is found.
| ma |
ma := ModuleAssembly core.
ma alias: '/one' to:
[ma documentRoot: (FileDirectory default directoryNamed: 'one').
ma directoryIndex: 'index.html index.htm'.
ma serveFiles].
ma addPlug:
[:request | HttpResponse fromString: '<a href="one">go to one</a>'].
(HttpService startOn: 8080 named: 'Example') module: ma rootModule.
processHttp
<Boolean> Subclasses should override this method to process
the current http request (accessed via HttpRequest current).
Answer true if a response has been made available (via
HttpResponse current:) and false if no response was generated.
ModSession
I add provide a session context to sub modules. To access the current session, use "KomSession current"...all requests passing through this module will be assigned a session, and responses from its subModules (if any) will be given a cookie to assign the session id.
Notes: This is basically a straight translation of the old Comanche session handling. Ideas for the future include:
- add the ability to use post/query fields instead of cookies
- add methods to assist in building Urls with session query parameters added
- be able to specify the cookie/field name that is used to store the session
- be able to tie sessions to a specific IP address (to minimize risk of man in the middle attacks)
Required Bindings:
HttpRequest current
Exported Bindings:
KomSession current
Example:
In the following example, we enable session tracking, set the session timeout to 30 minutes, and show the session id in the web browser.
| ma |
ma _ ModuleAssembly core.
ma trackSessions.
ma sessionTimeout: 30. "Sets the session timeout to 30 minutes"
ma addPlug:
[:request |
HttpResponse fromString:
('Your session id is: ', KomSession current id)].
(HttpService startOn: 8080 named: 'Example') plug: ma rootModule.
basicExtractSession:ifAbsent:
PRIVATE: answer the current session based on the 'SessionID' cookie
checkForCleanup
PRIVATE: run the cleanup if the last cleanup was more than
1 minute ago
cleanup
PRIVATE: iterate over all sessions and remove stale ones
extractSession:
<KomSession> extract or create a session for aRequest
initialize
Subclasses should redefine this method to perform initializations on instance creation
isSession
newSession
PRIVATE: creates a new Session and stores it
processHttp
<Boolean> Subclasses should override this method to process
the current http request (accessed via HttpRequest current).
Answer true if a response has been made available (via
HttpResponse current:) and false if no response was generated.
secondsToLive:
sessionsDo:
Iterate over sessions.
ModVhost
This module exports a string that is created based on a template. Other modules may access and use the resulting string as they see fit (using "ModVhost string"). ModVhostDoc uses the ModVhost string to establish a document root based on the virtual host name. ModVhostAlias uses the ModVhost string to look for a match when deciding whether or not to process its subModules. See the examples below for more details.
The format of the template string is designed to be compatible with Apache's virtual hosting support. The following is adapted from the Apache documentation:
-----
%% inserts a percent
%p inserts the socket port number (as it is known to the client)
%N.M inserts parts of the fully qualified domain name (FQDN, as it is know to the client)
N and M are used to specify substrings of the FQDN. N selects from the dot-separated components of the FQDN, and M selects characters within whatever N has selected. M is optional and defaults to zero if it isn't present; the dot must be present if and only if M is present. The interpretation is as follows:
0 the whole name
1 the first part
2 the second part
-1 the last part
-2 the penultimate part
2+ the second and all subsequent parts
-2+ the penultimate and all preceding parts
1+ and -1+ the same as 0
If N or M is greater than the number of parts available a single underscore is interpolated.
-----
The following is an example configuration for mass virtual hosting of file based content (it uses ModVhostDoc to interpret the host string produced by ModVhost):
| ma |
ma _ ModuleAssembly core.
ma virtualDocumentRoot: (FileDirectory default fullNameFor: '%0') do:
[ma directoryIndex: 'index.html index.htm'.
ma serveFiles].
(HttpService startOn: 8080 named: 'Example') module: ma rootModule.
In this example, the document root for the web server will be determined using the entire FQDN as the final element in the document root.
Of course, virtual hosting does not need to be restricted to file serving. Similar to ModAlias, you can use the virtual host name to control which modules get processed. The following is another example (it uses makes use of ModVhostAlias):
| ma |
ma _ ModuleAssembly core.
ma virtualHost: 'localhost' do:
[ma addPlug:
[ :request |
HttpResponse fromString: 'You are seeing content for localhost']].
ma addPlug:
[ :request |
HttpResponse fromString: 'You are seeing default content'].
(HttpService startOn: 8080 named: 'Example') module: ma rootModule.
In this example, you will see different content depending on whether you access the server using http://localhost:8080 or http://127.0.0.1:8080. Another example might be hosting Seaside (see http://www.beta4.com/seaside2 for information) applications on a separate host name.
getOffsetAt:
getSubstituteAt:host:andDo:
isVhost
pathTemplate:
processHttp
<Boolean> Subclasses should override this method to process
the current http request (accessed via HttpRequest current).
Answer true if a response has been made available (via
HttpResponse current:) and false if no response was generated.
string
string:
stringFromHost:
template
template:
validateForStack:problems:
Subclasses may wish to override this method to check that
prerequisite modules are on the stack above them
ModVhostAlias
See the class comments for ModVhost for using Comanche's virtual hosting features.
host
host:
processHttp
Get the host string and if we match, then process
our subModules.
validateForStack:problems:
Subclasses may wish to override this method to check that
prerequisite modules are on the stack above them
ModVhostDoc
Used for determining the document root for mass virtual hosting (where the virtual server name is incorporated into the document root). See the class comments for ModVhost for an example of virtual hosting.
documentRoot
validateForStack:problems:
Subclasses may wish to override this method to check that
prerequisite modules are on the stack above them
ModuleAssembly
I am a module assembly. I provide a convienient mechanism for assembling modules into a module hierarchy for use by an HttpService. The following is a very simple example of how to use a ModuleAssembly:
| ma |
ma _ ModuleAssembly core.
ma addPlug:
[ :request |
HttpResponse fromString: 'Hello World!!!'].
(HttpService startOn: 8080 named: 'Example') module: ma rootModule.
addAssembly:
addModule:
addPlug:
Blocks and MessageSends are compatible with the ComancheModule
protocol, so just add it as a module
alias:to:
authenticate:realm:
basicPushModule:
core
currentModule
directoryIndex:
documentRoot:
documentRoot:do:
initialize
Subclasses should redefine this method to perform initializations on instance creation
invalidContext:
logTo:
lookupMod:ifFound:ifNotFound:
moduleStack
new
notFound
popModule
pushModule:
pushModule:andDo:
Push aModule on our stack and evaluate aBlock, note, this directive
protects against an unbalanced stack because certain directives just
push a new module on the stack without popping it
rootModule
serveFiles
serverRoot:
sessionTimeout:
sessionTimeoutSeconds:
trackSessions
trackSessions:
virtualDocumentRoot:do:
virtualHost:do:
MultipartChunk
kom/4.12 (Comanche/4.12)
MultipartChunkHeader (kom/4.8; bolot 4/2/2001 18:52)
- in kom49 (or kom50) this will be used only for internal purposes
-- applications will access multipart form fields through regular fieldAt: technique
Comment from kom46:
- this is a hack (bolot 10/25/2000 17:17)
- store a part's (from a multipart message) header information:
-- header (raw)
-- properties (extracted and converted info, such as file-name, content-length, etc.)
contentType
fieldName
fileName
fileName:
from:
initializeFrom:
PRE: all keys in aDictionary are low-case
isSaved
multipartBoundary
multipartBoundary:
saveToStream:
This method is no longer dumb. It streams data chunk wise so it doesn't have to fit
into memory all at once. Answers the number of bytes written to the stream.
The logic for stripping 128 bytes from IE4/Mac is what makes it look complex.
setSavedFlag
stream
stream:
NetworkHost
I am an abstaction of a network host (in DNS parlance) and port number. I can abstract named or numbered host names (as allowed in a URI for example).
NetworkHost fromString: 'www.yahoo.com:8080'
NetworkHost fromString: '192.168.1.1:80'
domainString
fileSystemPath
fromNamePath:
fromString:
fullName
httpReference
initializeFromIPAddress:
initializeFromNamePath:
initializeFromString:
initializeFromString:defaultPort:
initializePortFromString:defaultPort:
isNamedHost
machineName
name
Answer a name for the receiver. This is used generically in the title of certain inspectors, such as the referred-to inspector, and specificially by various subsystems. By default, we let the object just print itself out..
nameString
port
type
vhostName:letter:
First get the name component