Rack middleware server crash - ruby

I am trying to write simple rack middleware like this:
class NewMiddleWare
NEW_STRING = <<BEGIN
my new content
BEGIN
def initialize(app)
#app = app
end
def call(env)
status, headers, response = #app.call(env)
response_body = ""
response.each {|part| response_body += part}
response_body += "#{NEW_STRING}"
headers["Content-Length"] = response_body.length.to_s
[status, headers, response_body]
end
end
when I run rackup, I got:
Unexpected error while processing request: undefined methodeachfor #<String:0x007fad313bdb30> in terminal.
I couldn't figure out the reason for this error. Response object
should be able to respond each right? Coz I saw some sample code doing
that.

From the Rack spec:
The Body must respond to each and must only yield String values. The Body itself should not be an instance of String, as this will break in Ruby 1.9.
In Ruby 1.8 Strings did respond to each, but that changed in 1.9.
The simplest solution would be to just return an array containing the string:
[status, headers, [response_body]]

Related

Return immediately from `call(env)` after providing a response

I have a Rack web server app to validate in call(env) the provided HTTP query string and return different responses immediately (with appropriate erb) if those bits of validations fail.
I'm calling the following method to provide a response:
def respond(http_status, http_headers, html_body = '')
# Provide HTTP response
html_body = yield if block_given?
[http_status, http_headers, [html_body]]
end
which I gleaned from the web.
I'm then calling respond(...) from different points inside my call(env) method rather like this:
def call(env)
case blah
when '/'
if validation_a_fails
respond(invalid_a)
else
set up a variable for later use...
end
if validation_b_fails
respond(invalid_b)
else
set up another variable for later use...
end
if validation_c_fails
respond(invalid_c)
else
set up something else for later use...
end
else # not root url
respond(404_situation)
end
end
end
I expected that a call to respond(invalid_a) would exit the call method immediately. However, it doesn't. Instead, the rest of the call method are executed.
How can I get the respond(...) to return immediately to the calling browser?
if validation_a_fails
return respond(invalid_a)
else
in your case you can't omit return because you have multiple if-elses so it'd go through them.

get response of grape endpoint with def after

I'm using Grape. I want to define a method that runs after the response value has been calculated for a request, I tried following this:
http://www.sinatrarb.com/intro.html#Filters
and ended up with:
after do
puts response
end
however response is not defined. Apparently within this block, self refers to Grape::Endpoint, since after runs after the endpoint handler, I should be able to find the response value, right? I tried self.body however this returns nothing - it does, however, let me change the value of the response, but I want to retrieve the response value that was generated by my handler.
Ahh, so I solved this using rack middleware:
class CaptureResponse < Grape::Middleware::Base
def call!(env)
#env = env
#app_response = #app.call(#env)
body = #app_response[2]
body = body.body if body.kind_of? Rack::BodyProxy
puts body
#app_response
end
end
use CaptureResponse
I have no idea why just slapping in use CaptureResponse in config.ru works but it does!

Bubblewrap HTTP -> Table View; method returns bubblewrap query instead of response data

I'm trying out Rubymotion and can't seem to do figure how to accomplish what seems like a simple task.
I've set up a UITableView for a directory of people. I've created a rails back end that returns json.
Person model has a get_people class method defined:
def self.get_people
BubbleWrap::HTTP.get("http://myapp.com/api.json") do |response|
#people = BW::JSON.parse(response.body.to_str)
# p #people prints [{"id"=>10, "name"=>"Sam"}, {etc}] to the console
end
end
In the directory_controller I just want to set an instance variable for #data to the array that my endpoint returns such that I can populate the table view.
I am trying to do #data = Person.get_people in viewDidLoad, but am getting an error message that indicates the BW response object is being passed instead: undefined methodcount' for #BubbleWrap::HTTP::Query:0x8d04650 ...> (NoMethodError)`
So if I hard code my array into the get_people method after the BW response block everything works fine. But I find that I am also unable to persist an instance variable through the close of the BW respond block.
def self.get_people
BubbleWrap::HTTP.get("http://myapp.com/api.json") do |response|
#people = BW::JSON.parse(response.body.to_str)
end
p #people #prints nil to the console
# hard coding [{"id"=>10, "name"=>"Sam"}, {etc}] here puts my data in the table view correctly
end
What am I missing here? How do I get this data out of bubblewrap's response object and in to a usable form to pass to my controllers?
As explained in the BW documentation "BW::HTTP wraps NSURLRequest, NSURLConnection and friends to provide Ruby developers with a more familiar and easier to use API. The API uses async calls and blocks to stay as simple as possible."
Due to async nature of the call, in your 2nd snippet you are printing #people before you actually update it. THe right way is to pass the new data to the UI after parsing ended (say for instance #table.reloadData() if #people array is supposed to be displayed in a UITableView).
Here's an example:
def get_people
BubbleWrap::HTTP.get("http://myapp.com/api.json") do |response|
#people = BW::JSON.parse(response.body.to_str)
update_result()
end
end
def update_result()
p #people
# do stuff with the updated content in #people
end
Find a more complex use case with a more elaborate explanation at RubyMotion async programming with BubbleWrap
Personally, I'd skip BubbleWrap and go for something like this:
def self.get_people
people = []
json_string = self.get_json_from_http
json_data = json_string.dataUsingEncoding(NSUTF8StringEncoding)
e = Pointer.new(:object)
hash = NSJSONSerialization.JSONObjectWithData(json_data, options:0, error: e)
hash["person"].each do |person| # Assuming each of the people is stored in the JSON as "person"
people << person
end
people # #people is an array of hashes parsed from the JSON
end
def self.get_json_from_http
url_string = ("http://myapp.com/api.json").stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)
url = NSURL.URLWithString(url_string)
request = NSURLRequest.requestWithURL(url)
response = nil
error = nil
data = NSURLConnection.sendSynchronousRequest(request, returningResponse: response, error: error)
raise "BOOM!" unless (data.length > 0 && error.nil?)
json = NSString.alloc.initWithData(data, encoding: NSUTF8StringEncoding)
end

Automatically encode Rack output with JSON when Content-Type is application/json

I've got a modular Sinatra app and I'd like to encode the output as JSON when the content-type dictates. At the moment I'm doing so manually in my routes:
get 'someroute' do
# content-type is actually set with a before filter
# included only for clarity
content_type 'application/json', :charset => 'utf-8'
# .. #
{:success=>true}.to_json
end
I'd prefer it to look like this:
get 'someroute' do
content_type 'application/json', :charset => 'utf-8'
# .. #
{:success=>true}
end
And I'd like to use Rack middleware to do the encoding if it detects the appropriate content-type.
I've been trying to get the following to work, but to no avail (the content-length gets borked - returns content-length of original content and not JSON encoded content):
require 'init'
module Rack
class JSON
def initialize app
#app = app
end
def call env
#status, #headers, #body = #app.call env
if json_response?
#body = #body.to_json
end
[#status, #headers, #body]
end
def json_response?
#headers['Content-Type'] == 'application/json'
end
end
end
use Rack::JSON
MyApp.set :run, false
MyApp.set :environment, ENV['RACK_ENV'].to_sym
run MyApp
Any pointers to get me back on track?
You’ve got everything right but one thing: Rack expects the body to be an object that responds to each but is not a string. Just put your body inside an array.
It’s probably not necessary, but if you want to set the content length by hand, just add it to the headers.
if json_response?
#body = [#body.to_json]
#headers["Content-Length"] = #body[0].size.to_s
end

How to get ALL of the URL parameters in a Sinatra app

Using the following Sinatra app
get '/app' do
content_type :json
{"params" => params}.to_json
end
Invoking:
/app?param1=one&param2=two&param2=alt
Gives the following result:
{"params":{"param1":"one","param2":"alt"}}
Params has only two keys, param1 & param2.
I understand Sinatra is setting params as a hash, but it does not represent all of the URL request.
Is there a way in Sinatra to get a list of all URL parameters sent in the request?
Any request in rack
get '/app' do
params = request.env['rack.request.query_hash']
end
I believe by default params of the same name will be overwritten by the param that was processed last.
You could either setup params2 as an array of sorts
...&param2[]=two&param2[]=alt
Or parse the query string vs the Sinatra provided params hash.
kwon suggests to parse the query string.
You can use CGI to parse it as follows:
require 'cgi'
get '/app' do
content_type :json
{"params" => CGI::parse(request.query_string)}.to_json
end
Invoking:
/app?param1=one&param2=two&param2=alt
Gives the following result:
{"params":{"param1":["one"],"param2":["two","alt"]}}
You can create a helper to make the process more friendly:
require 'cgi'
helpers do
def request_params_repeats
params = {}
request.env["rack.input"].read.split('&').each do |pair|
kv = pair.split('=').map{|v| CGI.unescape(v)}
params.merge!({kv[0]=> kv.length > 1 ? kv[1] : nil }) {|key, o, n| o.is_a?(Array) ? o << n : [o,n]}
end
params
end
end
You can then access the parameters in your get block:
get '/app' do
content_type :json
request_params_repeats.to_json
end

Resources