Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support listening on UNIX domain sockets #97

Open
fladi opened this issue Jul 4, 2023 · 13 comments
Open

Support listening on UNIX domain sockets #97

fladi opened this issue Jul 4, 2023 · 13 comments
Labels
enhancement New feature or request polar

Comments

@fladi
Copy link

fladi commented Jul 4, 2023

It would be nice if granian could also listen on UNIX domain sockets (UDS), leveraging filesystem permissions to controll access. Both nginx and apache2 (and possible more webservers) support proxying to UDS.

Funding

  • You can sponsor this specific effort via a Polar.sh pledge below
  • We receive the pledge once the issue is completed & verified
Fund with Polar
@ArtemIsmagilov
Copy link

ArtemIsmagilov commented Nov 23, 2024

@gi0baro, hi!
To add a listen unix socket or file descriptor as in gunicorn for example https://docs.gunicorn.org/en/stable/settings.html#bind it is necessary to remove the --host , --port parameters and add --bind param.

You will need to re-implement the handler depending on the passed parameter --bind . In Rust, we have an excellent strict parser that will determine which host, port, or path we want to use as a socket

This will be a critical change. Are you considering unix socket and file descriptor support as a listen server?
Do you plan to use this approach? How do you see the addition of this feature?

@gi0baro
Copy link
Member

gi0baro commented Nov 25, 2024

it is necessary to remove the --host , --port parameters and add --bind param.
This will be a critical change.

@ArtemIsmagilov not necessarily. We can adapt --host to also parse unix:// uri, or add an additional parameter --unix that will take precedence over --host if supplied.

You will need to re-implement the handler depending on the passed parameter --bind . In Rust, we have an excellent strict parser that will determine which host, port, or path we want to use as a socket

That's not the point to enable support though; the whole Granian code nowadays expects a TCPListener, so the actual change to support this would be wrap that in an enum, add the Unix support and patch the code to match the actual listener type.

Are you considering unix socket and file descriptor support as a listen server?
Do you plan to use this approach? How do you see the addition of this feature?

To be honest, I see this as a very low priority atm.
The only benefit for listening on unix sockets is by having Granian behind a reverse proxy on the same host, which is definitely a niche use case and not necessarily something we want to invest time onto – also given enabling keepalive on the proxy would provide similar performance, and supporting such feature would need to have 2 different code paths due to Windows support.

@ionelmc
Copy link

ionelmc commented Nov 25, 2024

UDS are quite popular with uwsgi users: https://github.com/search?q=socket%3D%2F+uwsgi&type=code

I believe most uwsgi users looking for an alternative would look for something with comparable performance or comparable feature parity, or both. You have the performance but not the feature parity.

This feature is as niche as gluing up a bunch of docker containers on the same host is - so is it really? One could even argue that granian is a niche performance server with just one way to run it, given how enormous uwsgi's feature set is...

At least give it some thought and outline how this could be implemented, or how much funding would change the priority on this.

@gi0baro
Copy link
Member

gi0baro commented Nov 25, 2024

I believe most uwsgi users looking for an alternative would look for something with comparable performance or comparable feature parity, or both. You have the performance but not the feature parity.

Granian never stated or aimed to be a feature complete alternative to uWSGI. It's definitely not the purpose of the project: as a matter of fact Granian started supporting RSGI and ASGI and added WSGI only later. It's also confusing to me to compare the features between the two projects: the world in 2024 is a very different place compared to when uWSGI project started (and thus designed its architecture).

This feature is as niche as gluing up a bunch of docker containers on the same host is

I don't get why you would need UDS for that, especially with containers: I expect developers to rely on orchestration utilities (like a trivial compose file) and thus rely on services – and thus TCP.
TBH here, I don't think the lack of UDS support is preventing anybody from switching from uWSGI or other servers to Granian, probably the amount of changes they need to make is already sizeable and make the additional change from UDS to TCP quite insignificant in the overall process.

At least give it some thought and outline how this could be implemented, or how much funding would change the priority on this.

I think my last comment was pretty clear. And in regards of funding, I don't generally change my schedule based on that; to me funding issues is more of a metric on how much business related entities are interested into a specific feature. But my general feeling about this is still the same: it's more about wether it makes sense for a feature to land on Granian based on how it should be used, rather than how it compares to other projects/environments/setups.
And, given the project is OSS released under the BSD license, nothing prevents anybody to open a PR to implement this (it will also take eventual funding pledged on this) or fork the project to implement this and other uWSGI features.

@ionelmc
Copy link

ionelmc commented Nov 25, 2024

I don't get why you would need UDS for that, especially with containers: I expect developers to rely on orchestration utilities (like a trivial compose file) and thus rely on services – and thus TCP.

Filesystem is just better for access control if you're connecting two things in the same machine. It's not that I need it, it's the fact that it is better than playing it loose with those ports.

Maybe it's just me, but to me this seems an essential feature in a setup where you'd have a nginx/whatever frontend. It simplifies a lot some things: you can shove 2 containers (1 for granian, 1 for nginx) in a kubernetes pod without having to worry about service and ports configuration - in kubernetes it's quite a bit more verbose than compose config).

For you this seems like nothing, just a drop in the ocean of changes if one would want to switch from uwsgi to granian but it's not like that, maybe it's just the last drop that would prevent someone from switching.

Anyway, I'd make a PR but Rust is a bit over my hear, for now 🤪

@gi0baro
Copy link
Member

gi0baro commented Nov 25, 2024

Filesystem is just better for access control if you're connecting two things in the same machine. It's not that I need it, it's the fact that it is better than playing it loose with those ports.

Debatable 🤷‍♂️

Maybe it's just me, but to me this seems an essential feature in a setup where you'd have a nginx/whatever frontend. It simplifies a lot some things: you can shove 2 containers (1 for granian, 1 for nginx) in a kubernetes pod without having to worry about service and ports configuration - in kubernetes it's quite a bit more verbose than compose config).

Again, I don't get why you want to bundle together nginx and Granian. Just because that's the way you operate uwsgi, it doesn't mean it should be the way to operate Granian. It doesn't need another proxy on top of it.

@ionelmc
Copy link

ionelmc commented Nov 26, 2024

Nginx serves static files and has more configurable tls. It's just better at being a frontend.

@gi0baro
Copy link
Member

gi0baro commented Nov 26, 2024

Nginx serves static files

Now that can be (IMO) a more interesting feature request for Granian.

and has more configurable tls

🤔 like? can you expand on this?

It's just better at being a frontend.

Better than what? Better at what?
Talking about performance, a super simple containerised env with nginx+uwsgi using UDS vs just granian (SSL handled by nginx in uwsgi):

# Granian
Benchmarking 128 connections @ https://localhost:8000/b for 15 second(s)
  Latencies:
    Avg      Stdev    Min      Max
    3.12ms   1.09ms   0.53ms   34.75ms
  Requests:
    Total: 609672  Req/Sec: 40654.12
  Transfer:
    Total: 96.70 MB Transfer Rate: 6.45 MB/Sec

# nginx + uwsgi UDS
Benchmarking 128 connections @ https://localhost:8000/b for 15 second(s)
  Latencies:
    Avg      Stdev    Min      Max
    4.92ms   1.73ms   1.52ms   129.07ms
  Requests:
    Total: 387658  Req/Sec: 25848.58
  Transfer:
    Total: 79.85 MB Transfer Rate: 5.32 MB/Sec
384 Errors: connection closed

So again, I don't see any particular reason to prioritise this atm.

@ionelmc
Copy link

ionelmc commented Nov 26, 2024

like? can you expand on this?

Basically all these options in here: https://ssl-config.mozilla.org/#server=nginx&version=1.26.0&config=intermediate&openssl=1.1.1w&guideline=5.7

To name a few I don't see an equivalent: ssl_stapling, ssl_ciphers, ssl_prefer_server_ciphers, ssl_protocols, ssl_dhparam, ssl_session_cache, ssl_session_timeout.

And there's the other less mainstream options: http://nginx.org/en/docs/http/ngx_http_ssl_module.html

I consider mozilla's ssl guide to have the "mainstream options".

Talking about performance,

Pretty sure you ain't testing a static file over there - that would be more of a apples-to-apples comparison.

I suspect I struck a nerve to make you run benchmarks for the sake of an argument, sorry. I don't want to annoy you but we can't both be right :-) I joke, if you laugh you have to implement this 😂

Nginx is a better frontend webserver because it can handle statics with lots of options, tls with lots of options, auth with lots of options, proxy with lots of options and protocols and so on, none of which granian can do natively. You could maybe do statics/auth/proxy with a python wsgi app but it will be slow, or with rsgi but it will be complicated enough to not be worth it compared to nginx's "batteries included". And I haven't even mentioned letsencrypt certificates, another quagmire of complexity.

It's the same deal with uwsgi ... it can do statics, it can do tls, it can do proxy but it's really crummy for that and you hit lots of rough edges. It's really hard to be a good frontend. I think it's easier to be a good backend.

You'll say "but why this frontend-backend" dichotomy but then I'll say I have more than one service, and more than one kind that I want to be exposed on a single frontend.

@gi0baro
Copy link
Member

gi0baro commented Nov 26, 2024

Basically all these options in here: https://ssl-config.mozilla.org/#server=nginx&version=1.26.0&config=intermediate&openssl=1.1.1w&guideline=5.7

To name a few I don't see an equivalent: ssl_stapling, ssl_ciphers, ssl_prefer_server_ciphers, ssl_protocols, ssl_dhparam, ssl_session_cache, ssl_session_timeout.

Fair enough, I encourage you to open an issue to request the ones you judge relevant to have in Granian.

Pretty sure you ain't testing a static file over there - that would be more of a apples-to-apples comparison.

App code here https://github.com/emmett-framework/granian/blob/master/benchmarks/app/wsgi.py, function b_short.
It's apples to apples as both systems serves the same endpoint through SSL. But feel free to run you own benchmarks :)

I suspect I struck a nerve to make you run benchmarks for the sake of an argument, sorry. I don't want to annoy you but we can't both be right :-)

It's not about being right – especially given we're arguing on opinions on how to deploy a Python WSGI application – I'm just giving you all the reasons why I won't work on this anytime soon ;)

Nginx is a better frontend webserver because it can handle statics with lots of options, tls with lots of options, auth with lots of options, proxy with lots of options and protocols and so on, none of which granian can do natively.

Again, I encourage you to open relevant issues for all those features if you think might be interesting to have those in Granian (regardless of UDS).

You'll say "but why this frontend-backend" dichotomy but then I'll say I have more than one service, and more than one kind that I want to be exposed on a single frontend.

That's not the argument.
It seems you're implying you need UDS support in Granian to do all of what you said, which is simply not true.

Here's my overall recap:

  • if you need UDS because that's the way to deploy uWSGI, then stop treating Granian as uWSGI
  • if you need to wrap Granian into nginx for no specific reasons, then you can either stop doing it or use a TCP socket instead
  • if you need to serve static files with nginx and the rest of your app with granian, you can do that using a TCP socket upstream
  • if you want to handle TLS in nginx, you can do that using a TCP socket upstream
  • if you are in the same machine and want to have multiple Granian instances, you can do that just binding to different localhost ports
  • if you're in kubernetes, I expect you have an ingress or a gateway somewhere that will do routing and TLS handling, so there's really no reason to have nginx in the same pod which runs Granian and thus there's no need to use UDS

So again, I confirm my previous statement: I don't see any reason to prioritise this. And I don't think it makes any sense to keep the discussion proceed any further unless it's about specific UDS use cases that are not achievable with TCP.

@ionelmc
Copy link

ionelmc commented Nov 26, 2024

But feel free to run you own benchmarks :)

I have and granian has appalling performance in a proxy setup - actual apples to apples results (both using proxy setup and wsgi):

+ rewrk -c 10 -d 60s -h https://nginx/uwsgi
Beginning round 1...
Benchmarking 10 connections @ https://nginx/uwsgi for 1 minute(s)
  Latencies:
    Avg      Stdev    Min      Max
    0.48ms   5.70ms   0.12ms   851.60ms
  Requests:
    Total: 1261262 Req/Sec: 21028.46
  Transfer:
    Total: 325.62 MB Transfer Rate: 5.43 MB/Sec

1259 Errors: connection closed

+ rewrk -c 10 -d 60s -h https://nginx/granian
Beginning round 1...
Benchmarking 10 connections @ https://nginx/granian for 1 minute(s)
  Latencies:
    Avg      Stdev    Min      Max
    1.52ms   1.73ms   0.23ms   28.48ms
  Requests:
    Total: 394073  Req/Sec: 6570.29
  Transfer:
    Total: 85.95 MB Transfer Rate: 1.43 MB/Sec

388 Errors: connection closed

I suspect you have compared nginx+uwsgi (a badly configured uwsgi at that) against a naked granian (no proxy) with the most efficient protocols available (http2/rsgi).

This is my nginx config: https://github.com/ionelmc/testbench/blob/main/docker/python/nginx.site#L42-L56 and granian config:
https://github.com/ionelmc/testbench/blob/main/docker-compose.test.yml#L57-L66
The wsgi app is pretty much your b_short function: https://github.com/ionelmc/testbench/blob/main/src/testbench_project/wsgi.py

About the other things:

* if you need to wrap Granian into nginx _for no specific reasons_, then you can either stop doing it or use a TCP socket instead
* if you need to serve static files with nginx and the rest of your app with granian, you can do that using a TCP socket upstream
* if you want to handle TLS in nginx, you can do that using a TCP socket upstream

I cannot use a dumb tcp proxy cause I need a http protocol to have adequate logging, buffering and other good stuff.

Can you look at the performance issue? Why is granian so slow in my proxy setup?

@gi0baro
Copy link
Member

gi0baro commented Nov 26, 2024

I suspect you have compared nginx+uwsgi (a badly configured uwsgi at that) against a naked granian (no proxy) with the most efficient protocols available (http2/rsgi).

You have to put more attention in what I write, as I pointed you to a WSGI app. And rewrk speaks HTTP/1.1 unless you specify the --http2 param.
The whole point was to enforce the concept Granian doesn't need a proxy on top of it, of course it's naked.
And your case is definitely not apples to apples, given you're using uwsgi facilities in nginx instead of proxy_pass.

I cannot use a dumb tcp proxy cause I need a http protocol to have adequate logging, buffering and other good stuff.

Can you look at the performance issue? Why is granian so slow in my proxy setup?

I'm starting feeling severe difficulties in trying to understand what you're trying to achieve here.
I kindly suggest you to stop bloating this issue with all this and eventually open up a discussion in the appropriate section if you have questions about Granian performance and/or configuration for WSGI applications.
It seems evident to me you don't have specific points to add to the OP.

@tomkins
Copy link

tomkins commented Jan 19, 2025

Adding a comment to this issue as it's fairly similar, it would be nice to have socket activation support - so listening on a file descriptor passed from the process manager (eg. systemd).

For flexibility, it would be fantastic to see:

  • Listen on a TCP socket (already done)
  • Listen on a UNIX socket (as explained above)
  • Listen on a file descriptor (then let systemd listen on TCP socket or UNIX socket, and not start the process until the first request is made)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request polar
Projects
None yet
Development

No branches or pull requests

5 participants