cremes.devlist at mac.com
2007-Jan-23 01:27 UTC
[Mongrel] Want feedback on Mongrel patch for handling partial PUT requests
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
Ezra Zygmuntowicz
2007-Jan-23 02:31 UTC
[Mongrel] Want feedback on Mongrel patch for handling partial PUT requests
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, cremes.devlist at mac.com 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 at rubyforge.org > http://rubyforge.org/mailman/listinfo/mongrel-users >-- Ezra Zygmuntowicz -- Lead Rails Evangelist -- ez at engineyard.com -- Engine Yard, Serious Rails Hosting -- (866) 518-YARD (9273)
cremes.devlist at mac.com
2007-Jan-23 03:59 UTC
[Mongrel] Want feedback on Mongrel patch for handling partial PUT requests
On Jan 22, 2007, at 8:31 PM, Ezra Zygmuntowicz wrote:> > 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, cremes.devlist at mac.com 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. >>(snip) I emailed the patches to Ezra. If anyone else wants them, please email me off list. I found a few bugs & problems after I actually typed this stuff in and ran it. Those bugs are fixed in the patches. cr
cremes.devlist at mac.com
2007-Jan-23 04:03 UTC
[Mongrel] Want feedback on Mongrel patch for handling partial PUT requests
On Jan 23, 2007, at 12:09 AM, Zed A. Shaw wrote:> On Mon, 22 Jan 2007 19:27:35 -0600 > cremes.devlist at mac.com 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. > > I''ll take a look this weekend, but you really should have tests to > go with this just to make sure I''m trying it the way you are trying > it.Here are the patches against Mongrel 1.0 plus a test file. -------------- next part -------------- A non-text attachment was scrubbed... Name: test_upload.rb Type: text/x-ruby-script Size: 7680 bytes Desc: not available Url : http://rubyforge.org/pipermail/mongrel-users/attachments/20070122/d93f08dc/attachment-0001.bin -------------- next part -------------- A non-text attachment was scrubbed... Name: handlers.diff Type: application/octet-stream Size: 267 bytes Desc: not available Url : http://rubyforge.org/pipermail/mongrel-users/attachments/20070122/d93f08dc/attachment-0002.obj -------------- next part -------------- A non-text attachment was scrubbed... Name: mongrel.diff Type: application/octet-stream Size: 2951 bytes Desc: not available Url : http://rubyforge.org/pipermail/mongrel-users/attachments/20070122/d93f08dc/attachment-0003.obj -------------- next part --------------> Also, seeing how it can improve or change the upload progress > problem (the fact that it sucks) would be a good first step.That problem was a bit out of scope for me so I don''t think this helps much. The common assumption is that people are using POST for uploads which this patch doesn''t address at all.> Otherwise, I''ll look and get back to you. Sorry I didn''t get in > touch with you earlier. Just been busy.No problem; we''re all busy! cr
Zed A. Shaw
2007-Jan-23 06:09 UTC
[Mongrel] Want feedback on Mongrel patch for handling partial PUT requests
On Mon, 22 Jan 2007 19:27:35 -0600 cremes.devlist at mac.com 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.I''ll take a look this weekend, but you really should have tests to go with this just to make sure I''m trying it the way you are trying it. Also, seeing how it can improve or change the upload progress problem (the fact that it sucks) would be a good first step. Otherwise, I''ll look and get back to you. Sorry I didn''t get in touch with you earlier. Just been busy. -- Zed A. Shaw, MUDCRAP-CE Master Black Belt Sifu http://www.zedshaw.com/ http://www.awprofessional.com/title/0321483502 -- The Mongrel Book http://mongrel.rubyforge.org/ http://www.lingr.com/room/3yXhqKbfPy8 -- Come get help.