Curl command to test JSON posts
1 | curl -i -H "Content-Type: application/json" -H "Accept: application/json" -X POST -d '{"user":{"username_or_email":"username","password":"password"}}' http://fas/test_json_post |
Lost sleep over JSON and Rack::PostBodyContentTypeParser
I’ve been fighting this issue the last couple nights. I wrote earlier about how Rack::PostBodyContentTypeParser can automagically turn a posted JSON object into a Rack / Sinatra params hash. So, I wrote some tests to make sure this was the case and moved on. Well, it turns out in real life things weren’t working and I couldn’t figure out why. Everything looked cool, but the hash wasn’t getting set when I did an AJAX call in the browser – everything was empty. I looked at everything, from the server, to the JS library, to the browser, to setting different content types in prototype.js etc… UGH!
The short of it is that Rack::PostBodyContentTypeParser requires exactly application/json in order to automagically turn the posted JSON object into Rack params and prototype.js (and jquery.js were adding an encoding type of charset=UTF-8 so the entire header entry was coming across as this CONTENT_TYPE: application/json; charset=UTF-8. So, as a fix, I’m just including the Rack::PostBodyContentTypeParser in the Sinatra application with one small change. Here’s the code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | module Rack # A Rack middleware for parsing POST/PUT body data when Content-Type is # not one of the standard supported types, like <tt>application/json</tt>. # # TODO: Find a better name. # class PostBodyContentTypeParser # Constants # CONTENT_TYPE = 'CONTENT_TYPE'.freeze POST_BODY = 'rack.input'.freeze FORM_INPUT = 'rack.request.form_input'.freeze FORM_HASH = 'rack.request.form_hash'.freeze # Supported Content-Types # ################## turned into regex so it matches type with encoding data... #APPLICATION_JSON = 'application/json'.freeze APPLICATION_JSON = /^application\/json/.freeze def initialize(app) @app = app end def call(env) case env[CONTENT_TYPE] when APPLICATION_JSON env.update(FORM_HASH => JSON.parse(env[POST_BODY].read), FORM_INPUT => env[POST_BODY]) end @app.call(env) end end end |
I tested that this worked by writing the following:
1 2 3 4 5 6 7 8 9 10 | def test_post_as_json_converts_to_params # sanity check that post with normal params works... post '/test_params_as_json', :param1=>"param one" assert_equal last_response.body,"params[:param1]=param one" post '/test_params_as_json', {:param1=>"param one"}.to_json, "CONTENT_TYPE"=>"application/json" assert_equal last_response.body,"params[:param1]=param one" # this is the problem, adding a charset to the content type seems to breaks rack-contrib/post_body_content_type_parser.rb post '/test_params_as_json', {:param1=>"param one"}.to_json, "CONTENT_TYPE"=>"application/json; charset=UTF-8" assert_equal last_response.body,"params[:param1]=param one" end |
Maybe I’ll start embedding code from gist…
Let’s see how this looks…
Couldn’t get that last thing to work so keeping it simple (stupid)
This could undoubtedly be more elegant, but it’s late and I want it to work now. May take another stab at it later…
TODO: limit the content types and only allow rendering if they are ok.
Setting up a before filter:
1 2 3 4 5 6 7 | before do # remove and grab the file extension request.path_info.sub! %r{\.([^\./]+)$}, '' @format=$1 || 'html' @charset=mime_type($1) || 'text/html' content_type @charset, :charset => 'utf-8' end |
and using a case statement:
1 2 3 4 5 6 7 8 9 10 11 | get "/home" do case @format when 'html' @stylesheet='home.css' haml :home, :layout=>:layout_simple when 'js' "{'js':true}" else pass end end |
Doing something different depending on file extension (MIME type) in Sinatra
This looks like a solution: sinatra-respond_to
Posted: March 31st, 2010 | Author: jay | Filed under: Code | Tags: extension, mime type, respond_to, sinatra | No Comments »Using Rack middleware to parse JSON
In attempting to AJAX-ize the site, I had the desire to handle JSON as if it were form post data. Queue a Rack middleware solution. rack-contrib contains a bunch of common middleware extensions, one being the horribly named PostBodyContentTypeParser. To get this working I added:
1 | require 'rack/contrib' |
with all of the rest of the required files.
Added:
1 | use Rack::PostBodyContentTypeParser |
to my application class
And went about over testing it like so:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | def test_json_creates_params_hash params_hash={"user"=>{"username"=>"testuser","email"=>"test@test.com","password"=>"pass1","password_confirmation"=>"pass1"}} post '/test_json', params_hash assert !last_request.params.blank? assert_equal params_hash, last_request.params assert last_response.ok? json_string="{\"user\":{\"password_confirmation\":\"pass1\",\"username\":\"testuser\",\"password\":\"pass1\",\"email\":\"test@test.com\"}}" post '/test_json', JSON(json_string) assert !last_request.params.blank? assert_equal params_hash, last_request.params assert last_response.ok? post '/test_json', json_string, "CONTENT_TYPE"=>"application/json" assert_equal last_request.env["CONTENT_TYPE"], "application/json" assert !last_request.params.blank? assert_equal params_hash, last_request.params assert last_response.ok? end |
AJAX-izing
Working on making the site more app-like. In doing so, found the Prototype.js-based library, LivePipe. The documentation is poor, but what little I’ve seen, I like.
Now, I’m trying to figure out how much I should “gracefully degrade” or not.
Posted: March 29th, 2010 | Author: jay | Filed under: Code | Tags: ajax, javascript, livepipe, prototype, prototype.js | No Comments »Close to perfection
Posted: March 29th, 2010 | Author: jay | Filed under: Fun | Tags: Bundy, Major Lazer, wonderful | No Comments »Blogging system mostly in place
Title pretty much says it all.
Posted: March 28th, 2010 | Author: jay | Filed under: Code | Tags: blog, function | No Comments »