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:
- Create a new batch resource via the usual CRUD methodology (POST, receive a location to the newly created resource).
- 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.
- “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?
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:
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.
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.
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.
Feed the constructed environment into a call to app.call(env). Repeat for
each operation.
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