Why can't I terminate Sinatra server? - ruby

Why can't I stop Sinatra server using the following:
post '/terminate' do
Thread.current.kill
end
I type in my browser:
localhost:<port>/terminate
but it never terminates the server, Sinatra says that it does not know such path. What could be the reason?

adzdavies is partially right, you don't hit the route because your browser is issuing a GET request and you've defined a post route, but exit won't work either, it'll just spit out an error at you. As will raising an Interrupt exception. Thread.current.kill just ends execution of the current thread, which seems to mean killing the current instance and the server will just spawn a new instance on the next request, it won't kill the server, the server has its own process.
require 'sinatra/base'
class SillyWaysToKillTheServer < Sinatra::Base
enable :inline_templates
get "/" do
erb :index
end
get '/terminate' do
exit # system exit!
end
get "/threadkill" do
Thread.current.kill
end
get "/exception" do
raise Interrupt, "Kill the server, please!"
end
run! if __FILE__ == $0
end
__END__
## layout
<html>
<body>
<%= yield %>
</body>
</html>
## index
<p>terminate</p>
<p>threadkill</p>
<p>exception</p>
Sinatra is a framework, not a server. The server has its own process and runs a little loop that starts new threads or forks (Thin uses threading as its model, Unicorn uses preforking, for example) or whatever which run the Sinatra code you've provided. To stop the server, either use Ctrl+c to interrupt it, or find the process number and use kill or send a SIGHUP via something like everyone else does. There may be some good reason for doing stopping a webserver like this, but I can't think of one, and maybe different servers would respond differently to the Thread kills and exits etc, but they still won't stop your server.
Go with the flow.

The browser will be doing a "GET" http request.
If you change to
get '/terminate' do
exit # system exit!
end
I think it will work.

Related

Changes to app.rb don't seem to go into effect

I need to test a GET request in sendRequest.js:
$.get( '/test/test2', {name: 'Larry', time: '2pm'} );
This sends the request fine and everything works on the JavaScript end, but obviously returns a 404 (route not found) so I added in app.rb:
get '/test/test2' do
logger.info "this is a test log"
end
I sent the request again, and I got the same 404.
This scenario originates from none of my changes in app.rb going into effect. I've deleted entire routes, commented stuff out, etc., nothing I do in app.rb seems to have any effect on the server.
Any ideas?
Have you tried stopping and restarting the server?
By default Sinatra won't know its code has changed, and Ruby will have loaded all the script into memory and won't look at the file.
Stopping the server, by using Cntrl+C then restarting the server will refresh Ruby's idea of what should be done.

Ruby code working on standalone Sinatra, but fails with Passenger on Apache2

I implemented a longpoll mechanism for my website, which keeps the connection to the client open for upto 30 seconds and only sends an answer if the resource requested changes within these 30 seconds, otherwise it sends status 400.
This is the code I used for the mechanism: (it is part of a Sinatra server)
get '/longpoll' do
state = Array.new( $callbacks )
0.upto 60 do |i|
if $callbacks != state
return json $callbacks
end
sleep 0.5
end
400
end
$callbacks is a global array, which gets changed when new callbacks are posted, or finished callbacks are deleted. My client just needs to dynamically update when new callbacks arrive or old callbacks disappear. On client-side a $.ajax()-Call requests the ressource and updates the website if an answer is received. Otherwise it just continues polling.
When I execute the Sinatra server on its own, everything works fine. The client gets the answers if anything changes, or error '400' after 30 seconds.
But when I start the whole thing using Passenger and Apache2 it always responds with '400' after 30 seconds, regardless if $callbacks changed or not. The rest of my Sinatra code is working fine within Apache2 and Passenger, and when I check the $callbacks object manually it shows the changes. It just does not recognize the change within the if-statement.

Modular Sinatra app returns 404's under Passenger

I have a modular Sinatra app which runs fine when executed with rackup. The config.ru file is defined as follows:
map '/' do
run My::Controllers::Default
end
map '/api' do
run My::Controllers::Api
end
When I run the app under nginx/passenger I get nothing but 404's, even for the '/' route. Suspecting that something was wrong with routing, I modified config.ru as follows:
run My::Controllers::Default
After restarting nginx, I was served the default page of the app. However, the default page of the app reaches into the api route to get some documentation to display, and that part returns a 404. Given that config.ru is able to run the Default controller, I'm sure that the issue has nothing to do with being able to load all of the relevant ruby files--which seems to be the problem in other related questions I've found on SO.
With that in mind I modified config.ru as follows:
map '/api' do
run My::Controllers::Api
end
run My::Controllers::Default
At this point I'm back to getting nothing but 404's, even for the '/' route. It seems that the map statement is confusing the webserver and making it unable to find the correct routes.
If I just run the app using rackup everything behaves as expected, so I'm really at a loss to explain what I'm seeing.
I remember this being the answer. Let me know if it works for you. If it does I'll "Accept" the answer so that others will find it.
Middleware
A bug in passenger prevents it from understanding the map statement in config.ru https://groups.google.com/forum/#!msg/phusion-passenger/PoEEp9YcWbY/1y0QL_i3tHYJ
class PassengerFix
def initialize(app)
#app = app
end
def call(env)
env["SERVER_NAME"] = env["HTTP_HOST"]
return #app.call(env)
end
end
config.ru
configure do
use PassengerFix
end

Sinatra::Streaming with Rack not chunking response

I'm having a rough time trying to get this simple streaming test to work using Sinatra and Rack.
In my stream.rb file, I have:
require 'sinatra'
require 'sinatra/streaming'
class StreamAPI < Sinatra::Base
helpers Sinatra::Streaming
get '/stream' do
stream do |out|
5.times do
out.puts "Hello!"
sleep 1
end
out.flush
end
end
run! if app_file == $0
end
And in my config.ru I have:
require 'rack'
require './stream.rb'
run StreamAPI
When I curl the url, I get "Hello!" 5 times, but all at once after 5 seconds. Looking at the headers I can see that Transfer-Encoding is set to Chunked. What I want is for the a "Hello!" to come through then another after a 1 second pause.
Edit: Along with the selected answer below, I also needed to add proxy_buffering off; to my NGINX configuration file.
This depends on which server you are using. From the Sinatra README:
Note that the streaming behavior, especially the number of concurrent requests, highly depends on the web server used to serve the application. Some servers, like WEBRick, might not even support streaming at all. If the server does not support streaming, the body will be sent all at once after the block passed to stream finishes executing.
It looks like you are using a server that doesn’t support streaming. If you switch to one that does (e.g. Thin or Puma) this should work.

POSTing an HTML form to remote.cgi - written in Ruby?

I am working on a website hosted on microsoft's office live service. It has a contact form enabling visitors to get in touch with the owner. I want to write a Ruby script that sits on a seperate sever and which the form will POST to. It will parse the form data and email the details to a preset address. The script should then redirect the browser to a confirmation page.
I have an ubuntu hardy machine running nginx and postfix. Ruby is installed and we shall see about using Thin and it's Rack functionality to handle the script. Now it's come to writing the script and i've drawn a blank.
It's been a long time and if i remember rightly the process is something like;
read HTTP header
parse parameters
send email
send redirect header
Broadly speaking, the question has been answered. Figuring out how to use the answer was more complicated than expected and I thought worth sharing.
First Steps:
I learnt rather abruptly that nginx doesn't directly support cgi scripts. You have to use some other process to run the script and get nginx to proxy requests over. If I was doing this in php (which in hind sight i think would have been a more natural choice) i could use something like php-fcgi and expect life would be pretty straight forward.
Ruby and fcgi felt pretty daunting. But if we are abandoning the ideal of loading these things at runtime then Rack is probably the most straight forward solution and Thin includes all we need. Learning how to make basic little apps with them has been profoundly beneficial to a relative Rails newcomer like me. The foundations of a Rails app can seem hidden for a long time and Rack has helped me lift the curtain that little bit further.
Nonetheless, following Yehuda's advice and looking up sinatra has been another surprise. I now have a basic sinatra app running in a Thin instance. It communicates with nginx over a unix socket in what i gather is the standard way. Sinatra enables a really elegant way to handle different requests and routes into the app. All you need is a get '/' {} to start handling requests to the virtual host. To add more (in a clean fashion) we just include a routes/script.rb into the main file.
# cgi-bin.rb
# main file loaded as a sinatra app
require 'sinatra'
# load cgi routes
require 'routes/default'
require 'routes/contact'
# 404 behaviour
not_found do
"Sorry, this CGI host does not recognize that request."
end
These route files will call on functionality stored in a separate library of classes:
# routes/contact.rb
# contact controller
require 'lib/contact/contactTarget'
require 'lib/contact/contactPost'
post '/contact/:target/?' do |target|
# the target for the message is taken from the URL
msg = ContactPost.new(request, target)
redirect msg.action, 302
end
The sheer horror of figuring out such a simple thing will stay with me for a while. I was expecting to calmly let nginx know that .rb files were to be executed and to just get on with it. Now that this little sinatra app is up and running, I'll be able to dive straight in if I want to add extra functionality in the future.
Implementation:
The ContactPost class handles the messaging aspect. All it needs to know are the parameters in the request and the target for the email. ContactPost::action kicks everything off and returns an address for the controller to redirect to.
There is a separate ContactTarget class that does some authentication to make sure the specified target accepts messages from the URL given in request.referrer. This is handled in ContactTarget::accept? as we can guess from the ContactPost::action method;
# lib/contact/contactPost.rb
class ContactPost
# ...
def action
return failed unless #target.accept? #request.referer
if send?
successful
else
failed
end
end
# ...
end
ContactPost::successful and ContactPost::failed each return a redirect address by combining paths supplied with the HTML form with the request.referer URI. All the behaviour is thus specified in the HTML form. Future websites that use this script just need to be listed in the user's own ~/cgi/contact.conf and they'll be away. This is because ContactTarget looks in /home/:target/cgi/contact.conf for the details. Maybe oneday this will be inappropriate, but for now it's just fine for my purposes.
The send method is simple enough, it creates an instance of a simple Email class and ships it out. The Email class is pretty much based on the standard usage example given in the Ruby net/smtp documentation;
# lib/email/email.rb
require 'net/smtp'
class Email
def initialize(from_alias, to, reply, subject, body)
#from_alias = from_alias
#from = "cgi_user#host.domain.com"
#to = to
#reply = reply
#subject = subject
#body = body
end
def send
Net::SMTP.start('localhost', 25) do |smtp|
smtp.send_message to_s, #from, #to
end
end
def to_s
<<END_OF_MESSAGE
From: #{#from_alias}
To: #{#to}
Reply-To: #{#from_alias}
Subject: #{#subject}
Date: #{DateTime::now().to_s}
#{#body}
END_OF_MESSAGE
end
end
All I need to do is rack up the application, let nginx know which socket to talk to and we're away.
Thank you everyone for your helpful pointers in the right direction! Long live sinatra!
It's all in the Net module, here's an example:
#net = Net::HTTP.new 'http://www.foo.com', 80
#params = {:name => 'doris', :email => 'doris#foo.com'}
# Create HTTP request
req = Net::HTTP::Post.new( 'script.cgi', {} )
req.set_form_data #params
# Send request
response = #net.start do |http|
http.read_timeout = 5600
http.request req
end
Probably the best way to do this would be to use an existing Ruby library like Sinatra:
require "rubygems"
require "sinatra"
get "/myurl" do
# params hash available here
# send email
end
You'll probably want to use MailFactory to send the actual email, but you definitely don't need to be mucking about with headers or parsing parameters.
CGI class of Ruby can be used for writing CGI scripts. Please check: http://www.ruby-doc.org/stdlib/libdoc/cgi/rdoc/index.html
By the way, there is no need to read the HTTP header. Parsing parametres will be easy using CGI class. Then, send the e-mail and redirect.

Resources