I would like to play with this a bit as it changes the way the mongrel_upload_progress does a request_aborted to cleanup. That plugin relies on the @body being nil to work but I think that could be worked around in your approach.
Can you please send an svn diff of this file? I don't feel like copy pasting all that jazz. Thanks -Ezra On Jan 22, 2007, at 5:27 PM, [EMAIL PROTECTED] wrote: > By default, Mongrel will delete the HTTP request body and short > circuit calling any handlers if a request is interrupted or > incomplete. Unfortunately, this breaks any attempt to correctly > handle a partial PUT. (BTW, PUT is *way* more efficient for uploads > compared to POST which requires a MIME parsing step.) So, about a > month ago I wrote up some patches to Mongrel 0.3.18 that allowed > Mongrel to properly handle partial PUT requests. I sent the patch & > tests to Zed for comment but never really heard back. > > It occurred to me that there might be a better way to handle things, > so here's my second stab at writing a patch that I hope will be > accepted into the Mongrel trunk. This patch was written against > Mongrel 1.0. The original patch detected if the request was a PUT and > ran various special case code. This approach makes handling partial > or interrupted requests in a generic way and pushes the > responsibility for accepting or rejecting them back onto the handler. > > If there's no chance this will be accepted into trunk, please let me > know. I'd be happy to provide this as a "plugin" to monkey-patch > mongrel for any people interested in this functionality. > > So, the patch touches three (3) classes and makes minor modifications. > > 1. class HttpHandler > Adds a single method called #accept_partial_requests? which is false > by default since most handlers do NOT want to handle a partial > request. Those that do can simply override this method in the > subclass. Used in HttpServer#process_client for determining if a > handler should get called. > > 2. class HttpRequest > Changes the logic in the #read_body method so it no longer deletes > the TempFile and sets @body to nil as a "signal" that the request was > interrupted. Adds a #attr_reader called #interrupted? for use outside > the class along with the appropriate initialization statement (this > is the new "signal"). Lastly, adds a method called #cleanup which > essentially restores the original activity from #read_body and > deletes the TempFile. > > 3. class HttpServer > Some logic changes to the #process_client method which is the main > workhorse of the class. It deletes the original statement that short > circuits the loop ( break if request.body == nil) and instead pushes > that check to the loop invoking all the handlers. The handler loop > now checks each handler to verify #handler.accept_partial_requests? > before passing a partial request to the handler. If it's a complete > request, the original calling semantics apply. This means all > existing handlers would continue to function as they did before. Only > those handlers which specifically override #accept_partial_requests? > will ever get called with a partial/malformed request. > > I'm a little concerned that these extra checks would be a performance > drag in the general case. Any feedback on improvements here would be > much appreciated. > > Lastly, the end of the method then calls #request.cleanup to delete > any lingering TempFile. > > > Anyway, enough talk... time for the code. It's all listed below, > though currently *untested*. I want to get feedback from Zed and the > group before I take the extra step of adding and testing all of this > (though I expect it to pass all tests just like the original patch). > > Thanks for your attention. > > cr > > ---- code below ---- > > module Mongrel > class HttpHandler > # NEW > def accept_partial_requests? > false > end > end > > class HttpRequest > # NEW > attr_reader :interrupted? > > def initialize > # original code plus... > self.interrupted? = false > end > > # modify #read_body so it does NOT delete the @body if an > exception is raised. > def read_body(remain, total) > begin > # write the odd sized chunk first > @params.http_body = read_socket(remain % Const::CHUNK_SIZE) > > remain -= @body.write(@params.http_body) > > update_request_progress(remain, total) > > # then stream out nothing but perfectly sized chunks > until remain <= 0 or @socket.closed? > # ASSUME: we are writing to a disk and these writes always > write the requested amount > @params.http_body = read_socket(Const::CHUNK_SIZE) > remain -= @body.write(@params.http_body) > > update_request_progress(remain, total) > end > rescue Object > STDERR.puts "ERROR reading http body: #$!" > $!.backtrace.join("\n") > @socket.close rescue Object > self.interrupted? = true # NEW > end > end > > # NEW > # clean up any lingering temp files > def cleanup > @body.delete if @body.class == Tempfile && interrupted? > end > end > > > class HttpServer > def process_client(client) > begin > # deleted for brevity > > while nparsed < data.length > nparsed = parser.execute(params, data, nparsed) > > if parser.finished? > # deleted for brevity > > if handlers > # deleted for brevity > > # select handlers that want more detailed request > notification > notifiers = handlers.select { |h| h.request_notify } > request = HttpRequest.new(params, client, notifiers) > > # break if request.body == nil ---- REMOVED > > # request is good so far, continue processing the > response > response = HttpResponse.new(client) unless > request.interrupted? # NEW statement modifier > > # Process each handler in registered order until we > run out or one finalizes the response. > if request.interrupted? > handlers.each do |handler| > handler.process(request, nil) if > handler.accept_partial_requests > end > else > # ORIGINAL logic > handlers.each do |handler| > handler.process(request, response) > break if response.done or client.closed? > end > end > > # NEW > # In the case of large file uploads the user could > close the socket, so the request is in a > # partial state. Clean up after it by removing any > TempFile that may exist. > request.cleanup if request.interrupted? > > # And finally, if nobody closed the response off, we > finalize it. > unless response.done or client.closed? > response.finished > end > else > # brevity! > end > > break #done > else > # deleted for brevity > end > end > rescue > EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,Errno::EBADF > # deleted for brevity > end > end > end > end > > _______________________________________________ > Mongrel-users mailing list > Mongrel-users@rubyforge.org > http://rubyforge.org/mailman/listinfo/mongrel-users > -- Ezra Zygmuntowicz -- Lead Rails Evangelist -- [EMAIL PROTECTED] -- Engine Yard, Serious Rails Hosting -- (866) 518-YARD (9273) _______________________________________________ Mongrel-users mailing list Mongrel-users@rubyforge.org http://rubyforge.org/mailman/listinfo/mongrel-users