What's the base URL for my app? - ruby

In Camping/Rack, how can I get the base URL for my app? I want to know so I can put it in an email it sends.
It might be (in development)
or
http://localhost:9292
or
http://localhost:80/game
or in production
http://fancy-snake.heroku.com

So far I have
url = #env['rack.url_scheme'] + "://" + #env['HTTP_HOST'] + R(LoginX, u.secret)
Which seems to work for the first and third cases. I don't know if it's write if the app is at localhost/prefix

You have to be a little careful with this, as there are a some subtle potential traps. The Rack::Request class will probably be helpful here.
First, you can’t really get the url for the app, as it may be responding to multiple urls (via Rack routes, Apache config, etc), so you’re looking at getting the url for the particular request. If you’re only serving requests from one url this won’t matter.
The scheme for the request is in the env hash under the rack.url_scheme, but this is only for the “last leg” of the request. If your app is behind a proxy of some sort (Nqinx, Apache etc.) then you want to get the scheme of the real request, not the request from the proxy to the machine your app is running on. If you’ve configured your proxy correctly it should be setting a header so you can tell what the original scheme was. Rack::Request has a scheme method that takes these headers into account.
The host for the url is probably in the env hash under the HTTP_HOST key, but this header is not necessarily present (admittedly that’s pretty unlikey nowadays). You should fall back on SERVER_NAME and SERVER_PORT. Additionally there’s the issue of handling proxied requests, you want the hostname of the original request, not the backend server. Again, Rack::Request provides host_with_port and host methods that deal with these issues.
Rack::Request also provides a base_url method that combines scheme and host, and additionally only includes the port if differs from the default (80 or 443).
The location that your app is mounted is in the env hash under the SCRIPT_NAME key. This would be /game in your second example, and can be empty if your app mounted at the root of your server. Again, Rack::Request provides a script_name method, although this one simply returns the value of the entry in the env hash.
So, in summary, you probably want to use something like this:
req = Rack::Request.new env
url = req.base_url + req.script_name
which looks pretty simple, but is taking care of various possibilities for you.
Additionally, you miight find the the Rack specification useful to have a read of, it contains details of the various entries that should be in the env hash.

Camping has a helper called URL which returns the absolute URL to your app:
URL() # => #<URL:http://test.ing/blog/>
URL() + "view/12" # => #<URL:http://test.ing/blog/view/12>
URL("/view/12") # => #<URL:http://test.ing/blog/view/12>

Related

Create a multi-website proxy with `http-proxy`

I'm using node-http-proxy to run a proxy website. I would like to proxy any target website that the user chooses, similarly to what's done by https://www.proxysite.com/, https://www.croxyproxy.com/ or https://hide.me/en/proxy.
How would one achieve this with node-http-proxy?
Idea #1: use a ?target= query param.
My first naive idea was to add a query param to the proxy, so that the proxy can read it and redirect.
Code-wise, it would more or less look like (assuming we're deploy this to http://myproxy.com):
const BASE_URL = 'https://myproxy.com';
// handler is the unique handler of all routes.
async function handler(
req: NextApiRequest,
res: NextApiResponse
): Promise<void> {
try {
const url = new URL(req.url, BASE_URL); // For example: `https://myproxy.com?target=https://google.com`
const targetURLStr = url.searchParams.get('target'); // Get `?target=` query param.
return httpProxyMiddleware(req, res, {
changeOrigin: true,
target: targetURLStr,
});
} catch (err) {
res.status(500).json({ error: (err as Error).message });
}
}
Problem: If I deploy this code to myproxy.com, and load https://myproxy.com?target=https://google.com, then google.com is loaded, but:
if I click a link to google images, it loads https://myproxy.com/images instead of https://myproxy.com?target=https://google.com/images, also see URL as query param in proxy, how to navigate?
Idea #2: use cookies
Second idea is to read the ?target= query param like above, store its hostname in a cookie, and proxy all resources to the cookie's hostname.
So for example user wants to access https://google.com/a/b?c=d via the proxy. The flow is:
go to https://myproxy.com?target=${encodeURIComponent('https://google.com/a/b?c=d')}
proxy reads the ?target= query param, sets the hostname (https://google.com) in a cookie
proxy redirects to https://myproxy.com/a/b?c=d (307 redirect)
proxy sees a new request, and since the cookie is set, we proxy this request into node-http-proxy using cookie's target.
Code-wise, it would look like: https://gist.github.com/throwaway34241/de8a623c1925ce0acd9d75ff10746275
Problem: This works very well. But only for one proxy at a time. If I open one browser tab with https://myproxy.com?target=https://google.com, and another tab with https://myproxy.com?target=https://facebook.com, then:
first it'll set the cookie to https://google.com, and i can navigate in the 1st tab correctly
then I go to the 2nd tab (without closing the 1st one), it'll set the cookie to https://facebook.com, and I can navigate facebook on the 2nd tab correctly
but then if I go back to the first tab, it'll proxy google resources through facebook, because the cookie has been overwritten.
I'm a bit out of ideas, and am wondering how those generic proxy websites are doing. Ideally, I would not want to parse the HTML of the target website.
The idea of a Proxy is to intercept the client requests, either by ports or by backend APIs, extract the URLs of requested resources, modify them and make those requests by self from servers, and modify responses and send them back to the client.
your first approach does this except modify responses and send back modified responses.
one way to do this is to edit all links in resources return by proxy to have your web address in them, only then send them as responses back to the client.
another way is to wrap the target site in a frame, as most web proxy sites do, and have a script to crawl the page and replace all links.
there is a small problem though. javascript-based requests are mostly hardcoded in the script and it is not an easy job to replace them.
your seconds approach sounds as if it would work better, but just a sound, nothing concrete I can say. implement a tab activity checker so you can change the cookie to your active tab. please check how-to-tell-if-browser-tab-is-active discussion about that

How to disable sessions in Yesod for a specific set of urls or a subsite?

I want to disable sessions for headless API endpoints, but I have to keep them turned on because this service also handles user logins.
However makeSessionBackend doesn't have access to Handler stuff or even current URI, like isAuthorizedSource does.
It appears to me that I should lift Client Session Backend code and sprinkle it with wrappers until the moment I can get at least textual path from that WAI Request.
Isn't there a better way to tell any bakend to ignore some routes like StaticR?
All of your points can be modified by overriding the makeSessionBackend method in the Yesod typeclass. Something like
instance Yesod App where
makeSessionBackend _ = fmap Just $ defaultClientSessionBackend expireTime filepath
where expireTime = 24 * 60

Simplest method of enforcing HTTPS for Heroku Ruby Sinatra app

I have an app I created on Heroku which is written in Ruby (not rails) and Sinatra.
It is hosted on the default herokuapp domain so I can address the app with both HTTP and HTTPS.
The app requests user credentials which I forward on to an HTTPS call so the forwarding part is secure.
I want to ensure my users always connect securely to my app so the credentials aren't passed in clear text.
Despite lots of research, I've not found a solution to this simple requirement.
Is there a simple solution without changing my app to Ruby rails or otherwise?
Thanks,
Alan
I use a helper that looks like this:
def https_required!
if settings.production? && request.scheme == 'http'
headers['Location'] = request.url.sub('http', 'https')
halt 301, "https required\n"
end
end
I can then add it to any single route I want to force to https, or use it in the before filter to force on a set of urls:
before "/admin/*" do
https_required!
end
Redirect in a Before Filter
This is untested, but it should work. If not, or if it needs additional refinement, it should at least give you a reasonable starting point.
before do
redirect request.url.sub('http', 'https') unless request.secure?
end
See Also
Filters
Request Object
RackSsl::Enforcer

Should I check if file exists before responding via .sendFile()?

Say I have this route handler in my Express.js app:
app.get('/files/:name', function (req, res) {
res.sendfile('path/to/files/' + req.params.name + '.html');
});
This path is used via Ajax on my site. I wonder if I should guard against situations where a file with the requested name does not exist on the server, in which case the server returns a 404 response. I can work with this, i.e. detect the 404 in the browser and act accordingly (e.g. display a message to the visitor). However, I'm not sure if this is a good approach. Is it OK to use the 404 response here, or am I supposed to try to avoid 404 responses if possible. (E.g. I could if a file with the requested name exists on the server and only use .sendFile() if it does.)
I'm worried that performing a manual check would only slow things down as .sendFile() already has this check built-in (i.e. it's possible to avoid this check and instead detect 404 responses in the browser which is what I'm doing right now and it works fine).
I would stick with returning a 404 instead, as that's the 404's purpose.I'm not sure there really is any other common/practical alternative.
Good luck.

How can I rewrite URLs in the Zeus web server for Mobile useragent?

I need to redirect anyone with a mobile user agent to a file called mobile.php.
My web hosting provider, Net Registry uses the Zeus web server.
Here's the script I've written from my research
RULE_1_START:
# get the document root
map path into SCRATCH:DOCROOT from /
match IN:User-Agent into $ with iPad|iPod|iPhone|Android|s+Mobile
if matched then
set OUT:Location = /mobile.php
endif
RULE_1_END:
I used the instructions on my host's site.
I pasted that into their console and it has worked to do redirects. Net registry have some odd console thing that you submit and it takes 10 minutes to update the zeus server config (annoying as hell).
Anyway my issue is that it redirects me to the wrong location:
So if you visit the site, with a user agent string that contains ipad|ipod|android|\s+mobile then you will trigger it ()
It takes me here:
http://example.com.au/mobile.php,%20catalog/index.php
I can't work out how to fix that, or why that happens because at the moment this file exists:
http://example.com.au/mobile.php
as does this one:
http://example.com.au/index.php. Contents of this file are:
<?php header("Location: catalog/index.php");
Any ideas on how I can make this work more like an apache .htaccess url Rewrite?
the official Zeus documentation
Fixed it by changing
set OUT:Location = /mobile.php
to
set URL = /mobile.php
From the manual...
Using Request Rewrite Scripts
To use the request rewriting functionality, create a script in the Zeus Request
Rewrite Scripting Language. The script contains instructions telling the
Virtual Server how to change the URL or headers of requests that match specified criteria.
The Virtual Server compiles the script, and (if the rewrite functionality is
enabled) uses it every time it receives a request. It runs the commands in the
script, changing the URL if it matches the specified criteria. Once the script is
finished, the Virtual Server continues processing the resulting URL.Zeus Web Server 4.3 User Guide
142 Configuring URL Handling
When changing the URL, the rewrite functionality can only change the local
part of it, that is, the part of the URL after the host name. For example, if a
user requests http://www.myhost.com/sales/uk.html, the rewrite
functionality can only make changes to /sales/uk.html. This means that
you cannot use the rewrite functionality to change the request to refer to a
file on another Virtual Server.
For example, the following script illustrates how to change requests for any
HTML files in the /sales directory so that the user receives them from the
/newsales directory instead:
match URL into $ with ^/sales/(.).html
if matched set URL=/newsales/$1.html
The rewrite functionality can also change the HTTP headers that were received
with a request, and create new HTTP headers to be returned to the user. For
example, the following script changes the HTTP host header, so that a request
for www.mysite.com/subserver is redirected to the Subserver
www.subserver.mysite.com:
match URL into $ with ^/([^/]+)/(.)$
if matched then
set IN:Host = www.$1.mysite.com
set URL = /$2
endif

Resources