-------------------------------------------------------------------------------- -- Title: LWResponse.lua -- Description: Like a square peg in a round hole -- Author: Raphaël Szwarc http://alt.textdrive.com/lua/ -- Creation Date: February 1, 2005 -- Legal: Copyright (C) 2005 Raphaël Szwarc -------------------------------------------------------------------------------- -- import dependencies local LUObject = require( "LUObject" ) local LUBundle = require( "LUBundle" ) local LULog = require( "LULog" ) local LUDate = require( "LUDate" ) local LUList = require( "LUList" ) local LUMap = require( "LUMap" ) local LUString = require( "LUString" ) local SocketWriter = require( "SocketWriter" ) local os = require( "os" ) local zlib = require( "zlib" ) require( "md5" ) -- define the class local super = LUObject local self = super() -- class variable(s) local _codes = nil local _content = nil -- constants local MaximumSize = 1000000 -- initialization method function self:init( aWriter, aRequest ) self = super.init( self ) self._writer = aWriter self._request = aRequest return self end -- method to access this response headers function self:headers() if self._headers == nil then local someHeaders = LUMap() if self:shouldClose() == true then someHeaders:put( "connection", "close" ) else someHeaders:put( "connection", "keep-alive" ) end someHeaders:put( "content-type", "text/html; charset=" .. self:encoding() ) someHeaders:put( "date", self:stringWithTime( os.time() ) ) someHeaders:put( "dav", "1" ) someHeaders:put( "ms-author-via", "DAV" ) someHeaders:put( "server", self:server() ) self._headers = someHeaders end return self._headers end -- method to access this response request function self:request() return self._request end -- method to access this response status function self:status() if self._status == nil then self._status = 200 end return self._status end -- method to set this response status function self:setStatus( aValue ) self._status = aValue return self end -- method to check if this response should close the connection function self:shouldClose() local aConnection = self:request():headers():get( "connection" ) if aConnection ~= nil then aConnection = aConnection:lower() if aConnection:find( "close" ) ~= nil then return true end if aConnection:find( "keep%-alive" ) ~= nil then return false end end if self:request():version() ~= self:version() then return true end return false end -- method to access this response status line function self:statusLine() local aVersion = self:version() local aStatus = self:status() local aDescription = self:codes():get( aStatus ) local aLine = ( "%s %s %s" ):format( aVersion, aStatus, aDescription ) return aLine end -- method to access this response writer function self:writer() return self._writer end -- method to handle the given content function self:handleContent( aContent ) local aLength = ( aContent or "" ):len() local aStatus = self:status() if aStatus >= 200 and aStatus <= 299 then local lastModified = self:headers():get( "last-modified" ) local etag = self:headers():get( "etag" ) if etag == nil and aLength > 0 and aLength < MaximumSize then etag = md5.digest( aContent ) self:headers():put( "etag", etag ) end if lastModified ~= nil then local ifModifiedSince = self:request():headers():get( "if-modified-since" ) if lastModified == ifModifiedSince then self:setStatus( 304 ) aContent = nil aLength = 0 end end if etag ~= nil then local ifNoneMatch = self:request():headers():get( "if-none-match" ) if etag == ifNoneMatch then self:setStatus( 304 ) aContent = nil aLength = 0 end end end if ( ( aStatus >= 100 ) and ( aStatus < 200 ) ) or ( aStatus == 204 ) then aContent = nil aLength = 0 end if ( aLength > 0 ) and ( aLength < MaximumSize ) then local anEncoding = self:request():headers():get( "accept-encoding" ) if anEncoding ~= nil then LULog:debug( anEncoding ) if anEncoding:lower():find( "gzip" ) ~= nil then local aCompressedContent = zlib.compress( aContent, 9, nil, 15 + 16 ) local aCompressedLength = aCompressedContent:len() if aCompressedLength < aLength then LULog:debug( aLength ) LULog:debug( aCompressedLength ) aContent = aCompressedContent aLength = aCompressedLength self:headers():put( "content-encoding", "gzip" ) self:headers():put( "vary", "accept-encoding" ) end end end end self:headers():put( "content-length", aLength ) return aContent, aLength end -- method to write this response content function self:writeContent( aContent, hasContent ) local aContent = self:handleContent( aContent ) local aBuffer = LUList() aBuffer:add( self:statusLine() ) for aKey, aValue in self:headers():iterator() do aBuffer:add( ( "%s: %s" ):format( LUString:capitalize( aKey ), aValue ) ) end aBuffer:add( "" ) if hasContent == nil or hasContent == true then aBuffer:add( aContent or "" ) else aBuffer:add( "" ) end LULog:debug( self:statusLine() ) LULog:debug( self:headers() ) self:writer():write( aBuffer:join( SocketWriter:eol() ) ) if self:shouldClose() == true then self:writer():close() end return self end -- method to write a default response status function self:writeStatus( aStatus, hasContent ) local aContent = self:content() local aDescription = self:codes():get( aStatus ) aDescription = ( "%s %s" ):format( aStatus, aDescription ) aContent = aContent:gsub( "%(v%:encoding%)", self:encoding() ) aContent = aContent:gsub( "%(v%:status%)", aDescription ) self:setStatus( aStatus ) self:writeContent( aContent, hasContent ) return self end -- method to convert a given time to a string function self:stringWithTime( aTime ) return os.date( "%a, %d %b %Y %H:%M:%S GMT", aTime - LUDate:timeZoneOffset() ) end -- method to access the response status codes function self:codes() if _codes == nil then local aBundle = LUBundle:bundleWithName( "LWResponse" ) local aFile = aBundle:fileWithName( "LWResponseCodes", "txt" ) _codes = LUMap():load( aFile ) end return _codes end -- method to access the response default content function self:content() if _content == nil then local aBundle = LUBundle:bundleWithName( "LWResponse" ) local aFile = aBundle:fileWithName( "LWResponse", "txt" ) _content = aFile:content() end return _content end -- method to access the response encoding function self:encoding() return "UTF-8" end -- method to access the response server function self:server() return "LW/0.3" end -- method to access the response version function self:version() return "HTTP/1.1" end return self