getting rack mapped directory inside app - ruby

Say I have a config.ru like:
map '/foo' do
run MyApp
end
and a Sinatra app like:
class MyApp < Sinatra::Base
use Rack::Session::File, key: 'rack.session', domain: 'my.domain.com', path: '/foo', expire_after: 86400 * 14, secret: 'mysecret'
end
How can I make MyApp agnostic to which request directory (/foo in this case) is used to access it? I have found that request.script_name contains this directory, but I cannot use it for the path: parameter of the use Rack::Session::File statement since it is not defined yet when starting the app from passenger, but only when requests are sent to the application later.

Unfortunately it's impossible even with dirty hacks.
So I suppose it's possible to do via two different ways:
External configuration file e.g. routes.yml (config.ru uses it for
map statement, application to discover such prefix in url);
Environment variable (I've chosen this because it's easy to configure on Heroku.

Related

How to use HTTP in hyperstack

I have made a basic install of a hyperstack rails app using hyperstack.org's installation instructions, trying to add a HTTP.get request in an after_mount callback.
Not really sure what else I could try, thought HTTP would be a standard option
class App < HyperComponent
include Hyperstack::Router
after_mount do
HTTP.get('/example.json')
end
render do
DIV() do
'App'
# NodeDisplay
# define routes using the Route psuedo component. Examples:
# Route('/foo', mounts: Foo) : match the path beginning with /foo and mount component Foo here
# Route('/foo') { Foo(...) } : display the contents of the block
# Route('/', exact: true, mounts: Home) : match the exact path / and mount the Home component
# Route('/user/:id/name', mounts: UserName) : path segments beginning with a colon will be captured in the match param
# see the hyper-router gem documentation for more details
end
end
end
the error received is:
Uncaught error: HTTP: uninitialized constant App::HTTP
in App (created by Hyperstack::Internal::Component::TopLevelRailsComponent)
in Hyperstack::Internal::Component::TopLevelRailsComponent
Simple answer: The HTTP library is not by default included in Opal or Hyperstack.
You can include it as part of the Opal jQuery wrapper, or with a minimal Opal Browser::HTTP library.
To add the jQuery wrapper to your Hyperstack application do the following:
Import the Hypestack jquery wrapper by adding
import 'hyperstack/component/jquery', client_only: true
to your config/initializers/hyperstack.rb file.
Then include the actual jquery javascript code in your assets:
If using webpacker run yarn add jquery in your terminal, and then add this line to the javascripts/packs/client_only.js file:
jQuery = require('jquery');
If not using webpacker instead add import 'jquery', client_only: true to the hyperstack initializer file.
If you just want to use the more minimal Browser::HTTP module, add
import 'browser/http
to your config/initializers/hyperstack.rb file.
After changing your hyperstack.rb you will have to clear the rails tmp cache by running rm -rf tmp/cache
Note: When using the browser version you will need to use Browser::HTTP instead of simply HTTP.

Does my Sinatra API application need a class declaration?

Someone developed an API application for me that works well. Unfortunately, it doesn't log anywhere and there are no logs at all. The app runs with the "rackup" command and sits behind an nginx web server. The Sinatra errors are not logged to the nginx logs.
The app.rb file looks something like this:
require './libs'
require 'sinatra'
require 'sinatra/namespace'
set :bind, '::1'
before do
content_type :json
headers 'Access-Control-Allow-Origin' => '*', 'Access-Control-Allow-Methods' => ['OPTIONS', 'GET', 'POST']
end
namespace '/api/v1' do
namespace '/getit/:thingtoget' do
helpers do
def myhelper1
<stuff>
end
def myhelper1
<stuff>
end
end
before do
myhelper1
myhelper2
end
get '/info' do
WidgetDomain::get_info(#va1, #var2).to_json
end
<more API paths here>
end
Now this is working fine. But now I want to introduce logging. So I looked at the Sinatra README and it said I could enable logging like so:
class MyApp < Sinatra::Base
configure :production, :development do
enable :logging
end
end
Do I put that above the namespace stuff? If I declare an app like that, does my namespace stuff need to be inside that app code somehow? I don't have a grasp of how this works at all.
It almost looks like it's trying to log in the nginx log, but here's what an error line looks like there:
2018/12/30 19:53:15 [error] 6615#0: *21522 connect() failed (111: Connection refused) while connecting to upstream, client: <someip>, server: api.example.com, request: "GET /api/v1/getit/thingtoget1/stuff/var1/var2/var3 HTTP/1.1", upstream: "http://[::1]:9292/api/v1/getit/thingtoget/stuff/var1/var2/var3", host: "api.example.com", referrer: "an HTML page from the nginx server"
It's almost like it's trying to connect back to the server to retrieve a message or something. But in case the logging has something to do with the way I've declared the server in the nginx configuration, here it is:
server {
listen 443;
listen [::]:443;
server_name api.myapp.com;
ssl on;
ssl_certificate /etc/nginx/ssl/myapp_com.pem;
ssl_certificate_key /etc/nginx/ssl/star_myapp_com.key;
location / {
proxy_pass http://localhost:9292;
}
}
Have a look at Sinatra's README in the Logging section but I tend to set up a logger outside of the Sinatra app in case other bits get bolted on or several apps are used together and I can use the same logger for all of them. Simplest way is a global variable (one of the few places they're acceptable, but it's not the only way) :
require 'mono_logger' # because it's thread safe
require 'pathname' # because paths aren't strings :)
log_path = Pathname(__dir__).join("logs/app.log")
$logger = MonoLogger.new(log_path)
$logger.level = MonoLogger::INFO
Then in a route or wherever:
get '/' do
$logger.info "here"
In my terminal:
$ cat logs/app.log
I, [2019-01-07T13:03:52.989415 #64378] INFO -- : here
As to configuration blocks, you don't need to worry about putting them within a class declaration unless you're using a modular app (see Modular vs. Classic Style in the README) and yours is in the classic style.
Configuration blocks aren't namespaced (Sinatra::Namespace handles things that take a route as an argument, like get and before) so follow convention and stick it near the top of the file.
Hope that helps.

URL Map an entire namespace using rack mount?

I have two modular Sinatra rack based applications: core.rb & project.rb:
# core.rb
class Core < Sinatra::Base
get "/" do
"Hello, world!"
end
end
# project.rb
class Project < Sinatra::Base
get "/" do
"A snazzy little Sinatra project I wish to showcase."
end
get "/foo" do
"If you see this, congratulations."
end
end
My goal is simply to map the entire /projects namespace to the Project class, wheras everything else is handled by the Core class. I found that you can do this to a limited extent in 2 ways:
# config.ru
require "./core.rb"
require "./projects.rb"
map "/projects" do
# Method #1: Using Sinatra's built-in Middleware
use Project
# Method #2: Using Rack::Cascade
run Rack::Cascade.new( [Project, Core] )
end
run Core
Both of the methods I tried above have the same effect. The routes / and /projects show up correctly, however when going to /projects/foo it throws an error which states it can't find the /foo route in my main core.rb file - which is NOT what I want. In other words it's looking for my /foo route in the wrong file :(
So, is it possible to map across the entire /projects namespace using rack-mount? And no, adding "/projects/" to all my routes in project.rb is not an option here I'm afraid.
Your config.ru file seems to work okay when I test it, but it looks a little confused. Here’s a simpler example that achieves the same thing:
map "/projects" do
run Project # note run, not use
end
run Core
Now any request where the path starts with /projects will be routed to the Project app, and all other will go to Core, which is associated with the root path automatically.

Using a rackup file to custom configure an application instance

tl;dr How can I get a single Sinatra app to start up very differently on different servers via customizations to config.ru?
Background
I have a single web application written using Sinatra that's run on different servers. Currently the codebase for these servers is forked because there are some non-trivial differences in the way (discrete) parts of them work. For example:
one server authenticates users via an intranet LDAP server, while another server uses a simpler local database table lookup.
one server uses an external cron job to periodically update some statistics, while another (Windows-based) server uses an internal sleepy Thread.
one server stores certain metadata in a local table, while another server pulls the metadata from an external Wiki via screen scraping (!).
…and so on.
I'd like to get these code bases completely shared (single Git repo). I envision that each server would have one slightly-differing configuration file that causes the app to be started up differently.
Abandoned Solutions
I could change the behavior of the app based on environment variables. As there are a not-tiny number of variations in behavior, I'd rather not hide the settings in environment variables.
I could create my own "server-settings.rb" file that is unique to each machine, require it in my app.rb, and then change the configuration there. However, this seems to possibly be re-inventing the wheel. I already have a file named config.ru for each server. Shouldn't I be using this?
The Current Code
My config.ru for the app currently is simply:
require ::File.join( ::File.dirname(__FILE__), 'app' )
run MyApp.new
And the app.rb that it requires is, in essence:
require 'sinatra'
require_relative 'helpers/login' # customized for LDAP lookup on this server
class MyApp < Sinatra::Application
use Rack::Session::Cookie, key:'foo.bar', path:'/', secret:'ohnoes'
set :protection, except: [:path_traversal, :session_hijacking]
configure :production do
# run various code that depends on server settings, e.g.
Snapshotter.start # there is no cron on this machine, so we do it ourselves
end
configure :development do
# run various code that depends on server settings
end
end
The Question
I'd like to make config.ru live up to its name, and have it look something like this:
require ::File.join( ::File.dirname(__FILE__), 'app' )
run MyApp.new( auth: :ldap, snapshot:false, metadata: :remote_wiki, … )
How can I modify my application to change its configuration behavior based on settings supplied via config.ru? Or is this an abuse of config.ru, trying to use it for totally the wrong thing?
As soon as I started reading the question the first answer to pop into my head was "environment variable" but you scotched that straight away :)
I'll go with a mixture of one of your coulds and the desired outcome code, as it's how I structure things…
Because I want to be able to test my applications more easily, I take most of the Ruby out of the config.ru and into a separate config.rb file and leave config.ru to be a bootstrap file. So my standard skel is:
config.ru
# encoding: UTF-8
require 'rubygems'
require 'bundler'
Bundler.setup
root = File.expand_path File.dirname(__FILE__)
require File.join( root , "./app/config.rb" )
# everything was moved into a separate module/file to make it easier to set up tests
map "/" do
run APP_NAME.app
end
app/config.rb
# encoding: utf-8
require_relative File.expand_path(File.join File.dirname(__FILE__), "../lib/ext/warn.rb")
require_relative "./init.rb" # config
require_relative "./main.rb" # routes and helpers
require 'encrypted_cookie'
# standard cookie settings
COOKIE_SETTINGS = {
:key => 'usr',
:path => "/",
:expire_after => 86400, # In seconds, 1 day
:secret => ENV["LLAVE"],
:httponly => true
}
module APP_NAME # overall name of the app
require 'rack/ssl' # force SSL
require 'rack/csrf'
if ENV["RACK_ENV"] == "development"
require 'pry'
require 'pry-nav'
end
# from http://devcenter.heroku.com/articles/ruby#logging
$stdout.sync = true
ONE_MONTH = 60 * 60 * 24 * 30
def self.app
Rack::Builder.app do
cookie_settings = COOKIE_SETTINGS
# more security if in production
cookie_settings.merge!( :secure => true ) if ENV["RACK_ENV"] == "production"
# AES encryption of cookies
use Rack::Session::EncryptedCookie, cookie_settings
if ENV["RACK_ENV"] == "production"
use Rack::SSL, :hsts => {:expires => ONE_MONTH}
end
# to stop XSS
use Rack::Csrf, :raise => true unless ENV["RACK_ENV"] == "test"
run App # the main Sinatra app
end
end # self.app
end # APP_NAME
The initial reason I did this was making it easy to run the app in specs:
shared_context "All routes" do
include Rack::Test::Methods
let(:app){ APP_NAME.app }
end
but it makes sense to me to keep this code with the rest of the application code, so to speak, as I can bundle things together, run other apps etc. I've used this to conditionally load different examples into the specs in a few projects (it helps cut down on duplicated effort and check the examples really work), so I don't see why you couldn't use it to conditionally load configurations.
This way you get to choose to use a conditional in the config.ru as to which config.rb file you would use, or use an env var in the config.rb as to which definiton of self.app to use , or pass in an options hash to self.app…
With your set up I'd rename the APP_NAME module to MyApp, and the Sinatra class to App (because quite often I'll have an website that runs a front end and an API, so the Sinatra classes get named by their function (App, API etc) and wrapped in a module named after the site) and end up with:
config.ru
map "/" do
run MyApp.app( auth: :ldap, snapshot:false, metadata: :remote_wiki )
end
config.rb
def self.app( opts={} )
opts = DEFAULT_OPTIONS.merge opts
# …
run App
end
It'll be interesting to see how other people tackle this.

Passing options to rackup via a Sinatra application

I'm new to ruby, learning Sinatra. While creating a Sinatra site by requiring 'sinatra' and setting up the routes directly under is pretty easy and rather well documented, creating an application by requiring 'sinatra/base' and writing a class that inherits from 'Sinatra::Base', while still relatively easy, is very poorly documented (maybe because it's a pretty recent feature of Sinatra).
And that's exactly what I am doing. I am not having too much trouble on the Sinatra part, however I am having a bit of trouble on the rackup/thin/server part. Apparently there are two ways to deploy the application: using Sinatra itself (using the run! method) and using a rackup file (typically config.ru).
Using Sinatra's run! method is extremely intuitive and works like a charm, but apparently it doesn't work if I want to deploy my app on heroku. As a matter of fact, almost all the Sinatra apps that I have encountered on GitHub use a config.ru file.
Using a rackup file might be equally intuitive, but I can't manage to understand how to pass options from the Sinatra app to the server (ir: the port). I tried to merge options to rackup's default options array:
MyApp::App.default_options.merge!(
:run => false,
:env => :production,
:port => 4567
)
run MyApp::App
by adding options directly to the app:
MyApp::App.set :port, 4567
MyApp::App.set :run, false
MyApp::App.set :env, :production
run MyApp::App
by setting options from within the application class:
module MyApp
class App < Sinatra::Base
set :port, 4567
set :run, false
set :env, :production
# ...
# config.ru
require 'app'
run MyApp::App
All the methods above failed, either by showing error messages or by just not taking any of the options into consideration. So is there any way to pass options to rackup/thin/the sever via a Sinatra app when using a rackup file? Or the options in questions should be passed directly to rackup/thin/the sever via command-line options?
As a reference to the problem, here is the little Sinatra application I am building: https://github.com/AzizLight/Wiki/
You're actully going to pass options to thin on the command line directly or via a configuration file. See all options:
$ thin -h
For production, use a configuration file:
$ thin -C thin-production.yml -R config.ru start
Here is an example thin-production.yml file:
---
address: localhost
port: 3020
servers: 4
max_conns: 1024
max_persistent_conns: 512
timeout: 30
environment: production
pid: tmp/pids/thin-production.pid
log: log/thin-production.log
daemonize: true
I know I'm resurrecting an ancient question here, but I came across another useful solution not yet mentioned. As stated in this rack wiki tutorial:
the first line starting with #\ is treated as if it was options, allowing rackup arguments to be specified in the config file.
So if you wanted to set your host to 0.0.0.0 and port to 5656, you would add the following line to the beginning of your config.ru file:
#\ -o 0.0.0.0 -p 5656

Resources