Open straight-shoota opened 6 years ago
I've looked at some other API's and I found a nice addition in Java's ServletRequest
: It has methods named getServerName()
and getServerPort()
which expose the values from the Host
header if available and default to the local address.
Go's http.Request
similarly has a Host
property which is either the value of the Host
header or the host included in the request URI. It may or may not include a port.
It should conceptually be on Request
instead of Response
, Context
should continue holding just a request and response.
@RX14 Having it on Request
would mean, these methods were also exposed in a client context, because this class is used as representing a request both in the client and server. A client request can't have any network connection data unless it is modified when it is being sent over a network connection. And it would still probably need different implementations for client and server context.
There are separate Response
classes for client and server, which makes it far more easily to incorporate access to connection data into the server response. Conceptually, this is not great, I agree.
But I don't think we can implement this with a sane API unless we had a separate server Request
type.
Which is why I said more than a year ago now we shouldn't use the same class for client and server requests. We either should have 2 or 4 classes: HTTP::Request
and HTTP::Response
or HTTP::{Client, Server}::{Request, Response}
. This weird middle ground is hacky.
See also #1722 and #453
HTTP::Request#remote_address
to retrieve the remote (i.e. client) address of an HTTP request from a server context. So the most requested feature has arrived. But there is more.With HTTP::Server
being able to listen on multiple interfaces, it might be interesting to expose information on the local interface as well. There could be #local_address
in the same manner. But I'm not sure whether that's worth it, storing the address in every request when it's only very rarely useful.
I don't think there is currently any means to discover whether a request was made over TLS or not. This has probably more relevant use cases. For example, when the server has interfaces for both HTTP and HTTPS, a handler might restrict some behaviour depending on whether its encrypted or not (for example form submissions, login cookies etc.). This would be similar to the X-Forwarded-Proto
header.
7610 finally added
HTTP::Request#remote_address
to retrieve the remote (i.e. client) address of an HTTP request from a server context. So the most requested feature has arrived. But there is more.With
HTTP::Server
being able to listen on multiple interfaces, it might be interesting to expose information on the local interface as well. There could be#local_address
in the same manner. But I'm not sure whether that's worth it, storing the address in every request when it's only very rarely useful.I don't think there is currently any means to discover whether a request was made over TLS or not. This has probably more relevant use cases. For example, when the server has interfaces for both HTTP and HTTPS, a handler might restrict some behaviour depending on whether its encrypted or not (for example form submissions, login cookies etc.). This would be similar to the
X-Forwarded-Proto
header.
Nginx can pass a header with the schema used to connect. Of course this isn't something Crystal's HTTP Server can do for you. But if you need this functionality now, it is available by means of a reverse proxy like Nginx (which given the amount of testing in production, among other factors https://stackoverflow.com/a/50522656 (answer discusses Ruby and Puma, same concepts apply with Crystal), you should be using in front of Crystal's application server)
I don't think there is currently any means to discover whether a request was made over TLS or not. This has probably more relevant use cases.
Bumping this. A use case I have is for generating URLs to a given route, which could be an absolute URL including the scheme/hostname. I think ideally I would use the scheme of the original request, but alas I'm not able to determine that.
As a follow up to my last comment, can someone confirm the way to determine if a request was HTTP
or HTTPS
is essentially to use X-Forwarded-Proto
from a trusted proxy and if you're not using a proxy you're just kinda out of luck and should default to HTTP
? I'm not missing something here? As from what I can tell that seems to be true.
EDIT:
I went and checked a few diff frameworks handle getting the scheme/if request is over https:
http
. The request type can be subclassed which changes the fallback to wsgi.url_scheme
which is set based on if the config has ssl enabled. At least via gunicorn
$_SERVER['HTTPS']
, tho I have no idea how this works as I can't even find it in the src code :shrug:I dont think Crystal exposes TLS connection info on the request. So in order to determine the scheme I guess I'd either have to assume https
if the server was started with bind_tls
or just keep it simple and require a trusted proxy and use http
otherwise? Or allow the user to define the base url/scheme in configuration to make it more explicit...
With #5776 upcoming,
HTTP::Server
can listen on multiple networks sockets - and even without that, it would be useful for a handler to be able to access information about the network connection used to process.My initial idea was to expose the socket as
Context#socket
but that would also allow to take over the IO which should be avoided (there'sResponse#upgrade
for that).There really just needs to be access to metadata about the socket, for example
local_address
andremote_address
(note thatremote_address
is not particularly useful to identify the actual client's IP because of proxies and whatnot). I don't know if there would be any other useful data.We could think about encapsulating the values in a struct like
Socket::Metadata
but I'm not sure it's worth adding another type for just two data points.A question is whether to expose it: on
Context
orResponse
. I'd say the most logical place would beContext
as it relates to both request and response. On the other hand, other network related features (such as the mentioned#upgrade
as well as#close
) are onResponse
.Another issue is how to implement it. It could either be just delegates to the
Socket
instance. this would be easy inReponse
because the network socket is already available internally as@io
. ForContext
it would either need access toResponse
's internal io or store a reference to the socket itself. A different solution would be to just copy the values to theContext
(orResponse
) instance.And the third question I'm not sure about, how this should behave when no network connection is involved (for example in specs that don't go through the network stack). Should it just return
nil
or a default value like the loopback address (but what would that be for Unix sockets?).