Embed webserver in desktop app: wxRuby and Sinatra - windows

I would love to give my windows based desktop applications a web interface and vice versa. My desktop application is written in wxRuby and the webserver is Sinatra (using webrick). The simplest idea was just to mash them together, this does not work.
This code does not work. The webserver and gui app do not run simultaneously. The desktop application runs first, and then after it is closed; sinatra starts.
require 'wx'
require 'sinatra'
configure do set :server, 'webrick' end
get '/' do
"Sinatra says hello"
end
class MyApp < Wx::App
def on_init
#frame = Wx::Frame.new( nil, -1, "Application" )
#frame.show
end
end
app = MyApp.new
app.main_loop
So I thought about changing the last two lines to
Thread.new do
app = MyApp.new
app.main_loop
end
Again. Desktop App runs until closed, then webserver starts. So I tried starting Sinatra in a Thread.
Thread.new do
require 'sinatra'
configure do set :server, 'webrick' end
get '/' do
"Sinatra says hello"
end
end
require 'wx'
class MyApp < Wx::App
def on_init
#frame = Wx::Frame.new( nil, -1, "Application" )
#frame.show
end
end
app = MyApp.new
app.main_loop
Again. Desktop App runs until closed, then webserver starts.
Please advise, but keep in mind that I would really like to just have one process. If your solution is two processes; I would like strong inter-process communication that does not require polling.
Thanks!
Jeff

This at least starts up, not sure if this breaks some threading rules.
require 'win32/process'
require 'sinatra/base'
class MyWebServer < Sinatra::Base
get '/' do
'Hello world!'
end
end
Thread.new do
MyWebServer.run! :host => 'localhost', :port => 4567
end
require 'wx'
class MyGui < Wx::App
def on_init
t = Wx::Timer.new(self, 55)
evt_timer(55) { Thread.pass }
t.start(1)
evt_idle { Thread.pass }
#frame = Wx::Frame.new( nil, -1, "Application" )
#frame.show
true
end
end
app = MyGui.new
app.main_loop

you can use bowline, but i'm not yet using it.

Related

Running Sinatra app inside Thread doesn't work

I'm trying to run a Sinatra app in a new Thread in order to also run some other thing inside my script but when I do:
require 'sinatra/base'
class App < Sinatra::Base
...some routes...
end
Thread.new do
App.run!
end
Nothing happens and Sinatra server is not started. Is there anything I'm missing in order to achieve this?
Finally I run the other ruby process in a Thread but from Sinatra application and it works just fine.
class App < Sinatra::Base
threads = []
threads <<
Thread.new do
Some::Other::Thing
rescue StandardError => e
$stderr << e.message
$stderr << e.backtrace.join("\n")
end
trap('INT') do
puts 'trapping'
threads.each do |t|
puts 'killing'
Thread.kill t
end
end
run!
end
And I added a control when Sinatra app is exited also kill the open thread.

Configuring rack-test to start the server indirectly

Here is my rack application:
class MainAppLogic
def initialize
Rack::Server.start(:app =>Server, :server => "WEBrick", :Port => "8080")
end
end
class Server
def self.call(env)
return [200, {},["Hello, World"]]
end
end
When actually run, it behaves as it should and returns "Hello World" to all requests. I'm having trouble convincing rack-test to work with it. Here are my tests:
require "rspec"
require "rack/test"
require "app"
# Rspec config source: https://github.com/shiroyasha/sinatra_rspec
RSpec.configure do |config|
config.include Rack::Test::Methods
end
describe MainAppLogic do
# App method source: https://github.com/shiroyasha/sinatra_rspec
def app
MainAppLogic.new
end
it "starts a server when initialized" do
get "/", {}, "SERVER_PORT" => "8080"
last_response.body.should be != nil
end
end
When I test this, it fails complaining that MainAppLogic is not a rack server, specifically, that it doesn't respond to MainAppLogic.call. How can I let it know to ignore that MainAppLogic isn't a rack server and just place a request to localhost:8080, because there server has started?
First thing: why the custom class to run the app? You can use the rackup tool, which is the de-facto standard for running Rack apps. Some more details on it here.
Your app code then becomes:
class App
def call(env)
return [200, {}, ['Hello, World!']]
end
end
and with the config.ru
require_relative 'app'
run App.new
you can start the app by running rackup in your project's directory.
As for the error, the message is pretty clear. rack-test expects, that the return value of app method would be an instance of a rack app (an object that responds to call method). Take a look what happens in rack-test internals (it's pretty easy to follow, as a tip—focus on these in given order: lib/rack/test/methods.rb#L30 lib/rack/mock_session.rb#L7 lib/rack/test.rb#L244 lib/rack/mock_session.rb#L30. Notice how the Rack::MockSession is instantiated, how it is used when processing requests (e.g. when you call get method in your tests) and finally how the call method on your app is executed.
I hope that now it's clear why the test should look more like this (yes, you don't need to have a server running when executing your tests):
describe App do
def app
App.new
end
it "does a triple backflip" do
get "/"
expect(last_response.body).to eq("Hello, World")
end
end
P.S.
Sorry for the form of links to rack-test, can't add more than 2 with my current points :P
Your app should be the class name, for example instead of:
def app
MainAppLogic.new
end
You have to use
def app
MainAppLogic
end
You shouldn't need to indicate the port for doing the get, because the rack app runs in the context of the tests; so this should be right way:
it "starts a server when initialized" do
get "/"
last_response.body.should be != nil
end
Also, as a recommendation prefer to use the new expect format instead of the should, see http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/
And your MainAppLogic, should be something like:
class MainAppLogic < Sinatra::Base
get '/' do
'Hello world'
end
end

How to test if some specific rack middleware is being used?

To be more particular, I'm talking about sentry-raven and sinatra here. I saw examples testing sinatra applications, or middlewares. But I didn't see ones testing if some particular middleware is present. Or should I be testing behavior, not configuration (or how should I call it)?
The important thing (I'd say) is the behaviour, but if you wish to check for middleware there are 2 ways I'd suggest after taking a delve into the Sinatra source (there are possibly much easier/better ways):
The env
In the Sinatra source there's a method that uses the env to check if a middleware is already present:
# Behaves exactly like Rack::CommonLogger with the notable exception that it does nothing,
# if another CommonLogger is already in the middleware chain.
class CommonLogger < Rack::CommonLogger
def call(env)
env['sinatra.commonlogger'] ? #app.call(env) : super
end
You could do the same thing in a route, e.g.
get "/env-keys" do
env.keys.inspect
end
It'll only show you the middleware if it's inserted something in env hash, e.g.
class MyBad
def initialize app, options={}
#app = app
#options = options
end
def call env
#app.call env.merge("mybad" => "I'm sorry!")
end
end
output:
["SERVER_SOFTWARE", "SERVER_NAME", "rack.input", "rack.version", "rack.errors", "rack.multithread", "rack.multiprocess", "rack.run_once", "REQUEST_METHOD", "REQUEST_PATH", "PATH_INFO", "REQUEST_URI", "HTTP_VERSION", "HTTP_HOST", "HTTP_CONNECTION", "HTTP_CACHE_CONTROL", "HTTP_ACCEPT", "HTTP_USER_AGENT", "HTTP_DNT", "HTTP_ACCEPT_ENCODING", "HTTP_ACCEPT_LANGUAGE", "GATEWAY_INTERFACE", "SERVER_PORT", "QUERY_STRING", "SERVER_PROTOCOL", "rack.url_scheme", "SCRIPT_NAME", "REMOTE_ADDR", "async.callback", "async.close", "rack.logger", "mybad", "rack.request.query_string", "rack.request.query_hash", "sinatra.route"]
It's near the end of that list.
The middleware method
There's also a method called middleware in Sinatra::Base:
# Middleware used in this class and all superclasses.
def middleware
if superclass.respond_to?(:middleware)
superclass.middleware + #middleware
else
#middleware
end
end
Call it in the class definition of a modular app and you can get the middlewares in an array:
require 'sinatra/base'
class AnExample < Sinatra::Base
use MyBad
warn "self.middleware = #{self.middleware}"
output:
self.middleware = [[MyBad, [], nil]]
There may be a way to get it from Sinatra::Application, but I haven't looked.
With help from ruby-raven guys, we've got this:
ENV['RACK_ENV'] = 'test'
# the app: start
require 'sinatra'
require 'sentry-raven'
Raven.configure(true) do |config|
config.dsn = '...'
end
use Raven::Rack
get '/' do
'Hello, world!'
end
# the app: end
require 'rspec'
require 'rack/test'
Raven.configure do |config|
logger = ::Logger.new(STDOUT)
logger.level = ::Logger::WARN
config.logger = logger
end
describe 'app' do
include Rack::Test::Methods
def app
#app || Sinatra::Application
end
class TestRavenError < StandardError; end
it 'sends errors to sentry' do
#app = Class.new Sinatra::Application do
get '/' do
raise TestRavenError
end
end
allow(Raven.client).to receive(:send).and_return(true)
begin
get '/'
rescue TestRavenError
end
expect(Raven.client).to have_received(:send)
end
end
Or if raven sending requests is in the way (when tests fail because of raven sending requests, and not because of the underlying error), one can disable them:
Raven.configure(true) do |config|
config.should_send = Proc.new { false }
end
And mock Raven.send_or_skip instead:
...
allow(Raven).to receive(:send_or_skip).and_return(true)
begin
get '/'
rescue TestRavenError
end
expect(Raven).to have_received(:send_or_skip)
...

Sinatra + Fibers + EventMachine

I would like to know to to pause a Root Fiber in ruby (if possible).
I have this Sinatra app, and I am making async calls to an external API with EventMachine.
I don't want to respond to the client until the api responds me.
For example, sleeping the Root Fiber in Sinatra until the EventMachine callback wake it up.
Thanks.
get '/some/route/' do
fib = Fiber.current
req = EM::SomeNonBlokingLib.request
req.callback do |response|
fib.resume(response)
end
req.errback do |err|
fib.resume(err)
end
Fiber.yield
end
EDIT
In your case you should spawn a Fiber for each request. So. Firstly create Rack config file and add some magick:
# config.ru
BOOT_PATH = File.expand_path('../http.rb', __FILE__)
require BOOT_PATH
class FiberSpawn
def initialize(app)
#app = app
end
def call(env)
fib = Fiber.new do
res = #app.call(env)
env['async.callback'].call(res)
end
EM.next_tick{ fib.resume }
throw :async
end
end
use FiberSpawn
run Http
Then your http Sinatra application:
# http.rb
require 'sinatra'
require 'fiber'
class Http < Sinatra::Base
get '/' do
f = Fiber.current
EM.add_timer(1) do
f.resume("Hello World")
end
Fiber.yield
end
end
Now you could run it under thin for example:
> thin start -R config.ru
Then if you will visit locakhost:3000 you'll see your Hello World message

Ctrl+C not killing Sinatra + EM::WebSocket servers

I'm building a Ruby app that runs both an EM::WebSocket server as well as a Sinatra server. Individually, I believe both of these are equipped to handle a SIGINT. However, when running both in the same app, the app continues when I press Ctrl+C. My assumption is that one of them is capturing the SIGINT, preventing the other from capturing it as well. I'm not sure how to go about fixing it, though.
Here's the code in a nutshell:
require 'thin'
require 'sinatra/base'
require 'em-websocket'
EventMachine.run do
class Web::Server < Sinatra::Base
get('/') { erb :index }
run!(port: 3000)
end
EM::WebSocket.start(port: 3001) do |ws|
# connect/disconnect handlers
end
end
I had the same issue. The key for me seemed to be to start Thin in the reactor loop with signals: false:
Thin::Server.start(
App, '0.0.0.0', 3000,
signals: false
)
This is complete code for a simple chat server:
require 'thin'
require 'sinatra/base'
require 'em-websocket'
class App < Sinatra::Base
# threaded - False: Will take requests on the reactor thread
# True: Will queue request for background thread
configure do
set :threaded, false
end
get '/' do
erb :index
end
end
EventMachine.run do
# hit Control + C to stop
Signal.trap("INT") {
puts "Shutting down"
EventMachine.stop
}
Signal.trap("TERM") {
puts "Shutting down"
EventMachine.stop
}
#clients = []
EM::WebSocket.start(:host => '0.0.0.0', :port => '3001') do |ws|
ws.onopen do |handshake|
#clients << ws
ws.send "Connected to #{handshake.path}."
end
ws.onclose do
ws.send "Closed."
#clients.delete ws
end
ws.onmessage do |msg|
puts "Received message: #{msg}"
#clients.each do |socket|
socket.send msg
end
end
end
Thin::Server.start(
App, '0.0.0.0', 3000,
signals: false
)
end
I downgrade thin to version 1.5.1 and it just works. Wired.

Resources