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.
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.
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.
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()
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.
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.
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:
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.
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.
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.
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.
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.
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.
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).
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?
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.
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.