views:

55

answers:

2

Within my Rails application, I'd like to generate requests that behave identically to "genuine" HTTP requests.

For a somewhat contrived example, suppose I were creating a system that could batch incoming HTTP requests for later processing. The interface for it would be something like:

  1. Create a new batch resource via the usual CRUD methodology (POST, receive a location to the newly created resource).
  2. Update the batch resource by sending it URLs, HTTP methods, and data to be added to the collection of requests it's supposed to later perform in bulk.
  3. "Process" the batch resource, wherein it would iterate over its collection of requests (each of which might be represented by a URL, HTTP method, and a set of data), and somehow tell Rails to process those requests in the same way as it would were they coming in as normal, "non-batched" requests.

It seems to me that there are two important pieces of work that need to happen to make this functional:

First, the incoming requests need to be somehow saved for later. This could be simply a case of saving various aspects of the incoming request, such as the path, method, data, headers, etc. that are already exposed as part of the incoming request object within a controller. It would be nice if there was a more "automatic" way of handling this--perhaps something more like object marshaling or serialization--but the brute force approach of recording individual parameters should work as well.

Second, the saved requests need to be able to be re-injected into the rails application at a later time, and go through the same process that a normal HTTP request goes through: routing, controllers, views, etc. I'd like to be able to capture the response in a string, much as the HTTP client would have seen it, and I'd also like to do this using Rails' internal machinery rather than simply using an HTTP library to have the application literally make a new request to itself.

Thoughts?

A: 

a straight forward way of storing the arguments should be serializing the request object in your controller - this should contain all important data

to call the requests later on, i would consider using the Dispatcher.dispatch class method, that takes 3 arguments: the cgi request, the session options (CgiRequest::DEFAULT_SESSION_OPTIONS should be ok) and the stream which the output is written to

roman
By what method would one serialize the request object?Marshal.dump(request) doesn't work: it complains that "no marshal_dump is defined for class Proc", which makes sense since at least one of the request object's attributes is a Proc. Many of the other attributes are also references to instantiated objects, which makes me believe that even if marshaling produced a result, later reconstitution wouldn't yield a valid request.I'd imagine that any of the other typical serialization methods (e.g., `to_yaml', `to_json') would suffer from the same issues.
Michael Wehner
A: 

Rack Middleware

After doing a lot of investigation after I'd initially asked this question, I eventually experimented with and successfully implemented a solution using Rack Middleware.

A Basic Methodology

In the `call' method of the middleware:

  1. Check to see if we're making a request as a nested resource of a transaction object, or if it's an otherwise ordinary request. If it's ordinary, proceed as normal through the middleware by making a call to app.call(env), and return the status, headers, and response.

  2. Unless this is a transaction commit, record the "interesting" parts of the request's env hash, and save them to the database as an "operation" associated with this transaction object.

  3. If this is a transaction commit, retrieve all of the relevant operations for this transaction. Either create a new request environment, or clone the existing one and populate it with the values saved for the operation. Also make a copy of the original request environment for later restoration, if control is meant to pass through the application normally post-commit.

  4. Feed the constructed environment into a call to app.call(env). Repeat for each operation.

  5. If the original request environment was preserved, restore it and make one final call to app.call(env), returning from the invocation of `call' in the middleware the status, headers, and response from this final call to app.call(env).

A Sample Application

I've implemented an example implementation of the methodology I describe here, which I've made available on GitHub. It also contains an in-depth example describing how the implementation might look from an API perspective. Be warned: it's quite rough, totally undocumented (with the exception of the README), and quite possibly in violation of Rails good coding practices. It can be obtained here:

A Plugin/Gem

I'm also beginning work on a plugin or gem that will provide this sort of interface to any Rails application. It's in its formative stages (in fact it's completely devoid of code at the moment), and work on it will likely proceed slowly. Explore it as it develops here:

See also

Michael Wehner
Also worth noting is that I did not fully explore roman's suggested answer, and it's possible that it's a viable alternative to the methodology I propose.
Michael Wehner