Any interest in adding a --cors option to python -m http.server? (original) (raw)
The stdlib http.server module is very handy for quick local static web serving during software development. Sometimes though you might want Javascript running in a web app in a separate local web server to request these static assets. Doing to will often lead to a Cross-Origin Resource Sharing error, since the http.server module doesn’t set the access-control-allow-origin header out of the box. Would there be any interest in adding a --cors command line option, which would add this header: access-control-allow-origin: *
to responses from the server?
This would be fairly simple to implement and hopefully be useful to users of http.sever.
mcepl (Matěj Cepl) May 16, 2025, 9:19am 2
That would be slight -1 from me. The purpose of http.server
is to be simple and insecure, useable just for development and testing. We have enough worry about maintaining http.*
and urllib.*
packages and I don’t think we need to maintain a production HTTP server as well.
If you want, you can create a package on the top of http.server
and maintain it on PyPI.
blhsing (Ben Hsing) May 16, 2025, 9:33am 3
But the proposal is about a very frequently occurring scenario during development.
This happened to me too when I wanted to test my web app hosted on a Linux dev server from a Windows-based browser.
I think the scenario happens frequently enough during development to justify an addition of such an option.
sirosen (Stephen Rosen) May 16, 2025, 11:14am 4
I see a challenge that needs some planning.
Browsers change policies over time, so what works for CORS policy today might stop working at some point in the future. Suppose the practice evolves such that currently good CORS headers become out of date. Browsers start warning but don’t outright reject the headers, but state that they’ll start rejecting them in one year. How will we update to whatever the new best practice is?
CORS isn’t all that complicated if you want the “Allow *” policy. Can this just be a recipe in the docs? Then, if browsers change, all that’s needed is a docs change.
sinoroc (sinoroc) May 16, 2025, 5:28pm 5
Maybe a simple way to set headers in general would be more helpful and easier to justify. CORS is just one static header on all responses, right?
elis.byberi (Elis Byberi) May 16, 2025, 6:33pm 6
It doesn’t get much easier than simply subclassing SimpleHTTPRequestHandler
:
import http.server
import socketserver
class CustomHandler(http.server.SimpleHTTPRequestHandler):
def end_headers(self):
# Add your custom headers here
self.send_header("Access-Control-Allow-Origin", "*")
self.send_header("X-Custom-Header", "MyCustomValue")
super().end_headers()
PORT = 8080
with socketserver.TCPServer(("", PORT), CustomHandler) as httpd:
print(f"Serving on port {PORT}")
httpd.serve_forever()
elis.byberi (Elis Byberi) May 16, 2025, 6:40pm 7
CORS is much more complex, though most often only Access-Control-Allow-Origin
is used. CORS is not just about Access-Control-Allow-Origin
.
Request headers
Origin
Host
Access-Control-Request-Method
Access-Control-Request-Headers
Response headers
Access-Control-Allow-Origin
Access-Control-Allow-Credentials
Access-Control-Expose-Headers
Access-Control-Max-Age
Access-Control-Allow-Methods
Access-Control-Allow-Headers
sinoroc (sinoroc) May 16, 2025, 7:20pm 8
My point was that it might be hard to justify that the work to add python -m http.server --cors
is worth it, but maybe it would be easy to justify that the work to add python -m http.server --header 'X-Custom-Header: MyCustomValue'
is worth it.
Rosuav (Chris Angelico) May 16, 2025, 8:44pm 9
Yeah, but if anyone wants all of that complexity, it’s quite reasonable to say “code it”.
I’d love to see an example use-case for the proposed option, though. Normally I only need to worry about CORS headers on requests made from JavaScript, and those aren’t generally to static files.
aisipos (Anton Ian Sipos) May 17, 2025, 2:47am 10
Thanks everyone for the feedback. There’s a few things I’ll add in response.
As an example use case, there is a project called Stlite which is a port of Streamlit to WASM using Pyodide. To make a Stlite project, the library will need to “mount” files on its local virtual filesytem to make available to the WASM Python interpreter. The underlying Javascript will make an HTTP request for these files, but without a proper Access-Control-Allow-Origin
header, the call will fail. This is only one particular use case, but I imagine with the popularity of Pyodide / Pyscript rising, the need for CORS headers when developing with them will rise.
I do realize it is possible, with a little coding, to already make http.server return the proper CORS headers. And I do realize that CORS is a much bigger area than one single header, but sending access-control-allow-origin: *
covers many cases you might need in development, I claim. If it’s a common enough use case, why not make it a command line option? One command line option is much easier than 15 lines of code.
Alternatively, as @sinoroc mentioned, we could perhaps introduce a generic command line option to set any header, for instance:
python -m http.server -H 'access-control-allow-origin:*'
This would be generally more useful, but also does cover the use case I was trying to solve here. A simple shell alias could make this a few keystrokes away for those who used it often. It is though a “bigger” feature in scope (although still rather easy to implement), which is why I though of the “smaller” --cors
feature first.
Rosuav (Chris Angelico) May 17, 2025, 3:04am 11
Thanks, that makes sense.
I’m still dubious about the value of a dedicated --cors
parameter, but would definitely be in favour of a simple -H
/--header
to add one or more headers. There are other ways that that could be useful, too.
blhsing (Ben Hsing) May 19, 2025, 2:19am 12
The Access-Control-Allow-Origin: *
header has been stable since day 1 of the original W3C draft in 2009 and has never evolved. That’s 16 years and counting. It’s hard to imagine a breaking change to it any time in the future.
I believe http.server
is meant for quick testing during the experimental phases of development, during which time it is undesirable to restrict access. All of the complexity offered by the other CORS headers you listed impose additional restrictions over the default behavior, while Access-Control-Allow-Origin: *
is the only one that removes restrictions from the default behavior. It is therefore the header needed by a vast majority of use cases during development, which is exactly what http.server
is good for.
That’s still a lot of code compared to a simple option that can be enabled with 7 keystrokes in the command line on a whim.
As web apps become increasingly single-page, more and more assets are dynamically loaded and the value of a dedicated --cors
option only becomes more and more apparent. A new -H/--header
can be added alongside a new --cors
option, but I believe that the Access-Control-Allow-Origin: *
header has enough use cases to justify a dedicated option.
sirosen (Stephen Rosen) May 21, 2025, 3:11pm 13
I do not find it difficult to imagine at all. Web standards evolve – the fact that a standard is very old and stable does not make it immune to this. Especially given that this is a security standard, and someone may discover a novel attack vector in the next week, and that would have serious implications.
You may disagree with me, and I respect that opinion as very much legitimate.
Finding common ground, I expect you would agree with the following:
Although it is not inconceivable that the standard could change, it has a very long demonstrated history of stability. After 16 years of no change, and no active, known working groups seeking to supersede or supplement the standard with new mechanisms, we can reasonably assume that a CPython feature which adds support for the standard will not risk being broken by new standards in the near future.
But “very unlikely” is as different from “impossible” as the numbers 0.01 and 0. So I stand by my opinion that this needs some thought and care.
blhsing (Ben Hsing) May 21, 2025, 3:26pm 14
By your logics Python should not include any http.*
modules at all because Web standards evolve. Everyone should stick to sending raw HTTP headers and body over TCP/IP with the socket
module.
Once again http.server
is meant for easy, quick proof of concept during development, during which time any security whatsoever is not only unneeded but a hindrance. It’s therefore perfectly reasonable for http.server
to offer the proposed --cors
option to minimize its security.
ericvsmith (Eric V. Smith) May 21, 2025, 3:45pm 15
I reach the opposite conclusion: there’s no way an http.server
instance should be available without some protection. And this is especially true during development, when server and application bugs are most likely to exist.
blhsing (Ben Hsing) May 21, 2025, 3:48pm 16
Personally I use http.server
only in my private network, accessible only by myself and not exposed to the Internet. I think it can be documented that is how http.server
–especially with the --cors
option–is intended to be used (the docs actually has such verbiage already).
sirosen (Stephen Rosen) May 22, 2025, 12:02am 17
This is reducto ad absurdum. I think it’s bordering on a bad faith reading of what I’m saying.
Rosuav (Chris Angelico) May 22, 2025, 1:01am 18
Protection, I absolutely agree with. For example, if you run python3 -m http.server -d /tmp
and then wget http://127.0.0.1:8000/../etc/passwd
then it should error out (which it does). In that sense, basic security and protection are utterly vital. If anyone finds a way to exploit http.server in its normal form, that should be fixed.
But CORS isn’t about protecting the server. Usually, CORS headers are there to govern specific uses of dynamic requests from JavaScript (for example, making sure that your web app doesn’t send cookies belonging to page X as part of a request to page Y). This is partly why I was surprised that a static server would need CORS headers, and asked for clarification (which was given above - there’s legit, though uncommon, uses for this).
Having a simple option to add a fixed header Access-Control-Allow-Origin: *
would not open up the server to any sort of attack. If it’s the right tool for someone’s use-case, it will be of value. It won’t affect anything other than requests from JavaScript apps running in pages that did not themselves come from this same server; any attack that could be conducted by making use of this header could identically be conducted from a non-browser-context, and would then bypass CORS checking.
I’m personally of the opinion that it’d be better to spell this -h "Access-Control-Allow-Origin: *"
rather than --cors
as that would be a more generally-useful tool, but either way, this shouldn’t be a security risk for the server itself, with the possible exception of interactions with --cgi
mode; perhaps a line in the docs stating that this combination requires extra care? But, again, if it’s spelled as --header/-h
then it would be easy for someone to do -h "Access-Control-Allow-Origin: some-specific-origin"
to mitigate that.
Or - maybe --cors
would be equivalent to --cors=*
and then you can specify an origin explicitly if you prefer?
blhsing (Ben Hsing) May 22, 2025, 2:10am 19
That’s how I felt when you insinuated that a written standard that has been stable and used literally everywhere since it was established 16 years ago–an eternity by Web standards–may be too risky for Python to support. If that isn’t enough of a standard for Python to include a battery for I can think of more than a dozen modules that Python should not have included in the standard library because they are based on standards that have evolved in the last 16 years.
What the Access-Control-Allow-Origin: *
header does, why it was needed in the first place, when it should be used, and what implications it carries, are all well known and well documented. The value that this header adds aligns well with the core use cases of the http.server
module. Any “risks” that it carries are again well known and can be documented with the --cors
option.
Yeah that’s what I was going to suggest and you beat me to it. Good explanations too.
sirosen (Stephen Rosen) May 22, 2025, 2:30am 20
I don’t recall putting words in your mouth or rephrasing your opinion for you in a way intentionally designed to make your opinion look ridiculous. In fact, I don’t disrespect your opinion and have tried to be very explicit about thinking that it’s a reasonable position with which I disagree.
But I do disagree with you. Trying to pull rhetorical tricks instead of taking disagreement seriously makes this conversation very difficult, maybe even impossible.
I think this should be a documented example, plain and simple.