Can I set a header with python's SimpleHTTPServer? - python

I'm using SimpleHTTPServer to test some webpages I'm working on. It works great, however I need to do some cross-domain requests. That requires setting a Access-Control-Allow-Origin header with the domains the page is allowed to access.
Is there an easy way to set a header with SimpleHTTPServer and serve the original content? The header would be the same on each request.

This is a bit of a hack because it changes end_headers() behavior, but I think it's slightly better than copying and pasting the entire SimpleHTTPServer.py file.
My approach overrides end_headers() in a subclass and in it calls send_my_headers() followed by calling the superclass's end_headers().
It's not 1 - 2 lines either, less than 20 though; mostly boilerplate.
#!/usr/bin/env python
try:
from http import server # Python 3
except ImportError:
import SimpleHTTPServer as server # Python 2
class MyHTTPRequestHandler(server.SimpleHTTPRequestHandler):
def end_headers(self):
self.send_my_headers()
server.SimpleHTTPRequestHandler.end_headers(self)
def send_my_headers(self):
self.send_header("Access-Control-Allow-Origin", "*")
if __name__ == '__main__':
server.test(HandlerClass=MyHTTPRequestHandler)

I'd say there's no simple way of doing it, where simple means "just add 1-2 lines that will write the additional header and keep the existing functionality". So, the best solution would be to subclass the SimpleHTTPRequestHandler class and re-implement the functionality, with the addition of the new header.
The problem behind why there is no simple way of doing this can be observed by looking at the implementation of the SimpleHTTPRequestHandler class in the Python library: http://hg.python.org/cpython/file/19c74cadea95/Lib/http/server.py#l654
Notice the send_head() method, particularly the lines at the end of the method which send the response headers. Notice the invocation of the end_headers() method. This method writes the headers to the output, together with a blank line which signals the end of all headers and the start of the response body: http://docs.python.org/py3k/library/http.server.html#http.server.BaseHTTPRequestHandler.end_headers
Therefore, it would not be possible to subclass the SimpleHTTPRequestHandler handler, invoke the super-class do_GET() method, and then just add another header -- because the sending of the headers has already finished when the call to the super-class do_GET() method returns. And it has to work like this because the do_GET() method has to send the body (the file that is requested), and to send the body - it has to finalize sending the headers.
So, again, I think you're stuck with sub-classing the SimpleHTTPRequestHandler class, implementing it exactly as the code in the library (just copy-paste it?), and add another header before the call to the end_headers() method in send_head():
...
self.send_header("Last-Modified", self.date_time_string(fs.st_mtime))
# this below is the new header
self.send_header('Access-Control-Allow-Origin', '*')
self.end_headers()
return f
...

# coding: utf-8
import SimpleHTTPServer
import SocketServer
PORT = 9999
def do_GET(self):
self.send_response(200)
self.send_header('Access-Control-Allow-Origin', 'http://example.com')
self.end_headers()
Handler = SimpleHTTPServer.SimpleHTTPRequestHandler
Handler.do_GET = do_GET
httpd = SocketServer.TCPServer(("", PORT), Handler)
httpd.serve_forever()

While this is an older answer, its the first result in google...
Basically what #iMon0 suggested..Seems correct?..Example of doPOST
def do_POST(self):
self.send_response()
self.send_header('Content-type','application/json')
self.send_header('Access-Control-Allow-Origin','*')
self.end_headers()
sTest = {}
sTest['dummyitem'] = "Just an example of JSON"
self.wfile.write(json.dumps(sTest))
By doing this, the flow feels correct..
1: You get a request
2: You apply the headers and response type you want
3: You post back the data you want, be this what or how ever you want.,
The above example is working fine for me and can be extended further, its just a bare bone JSON post server. So i'll leave this here on SOF incase someone needs it or i myself come back in a few months for it.
This does produce a valid JSON file with only the sTest object, Same as a PHP generated page/file.

Related

How can I print all incoming data in Pythons Tornado?

I'm trying to get a basic grasp on what the communication is like between an ajax request and tornado, but I can't find any functions which give me something I can pass to print()
I've checked the API http://www.tornadoweb.org/en/stable/web.html and every function with the word "get" in it seems to require that I first know the name of the thing I'm trying to get.
I'm not quite there yet with my understanding, and would like to start by just printing everything there is to print. All the headers, all the data, going in and out.
How do I do this?
#pseudo code
class MainHandler(tornado.web.RequestHandler):
def get(self):
everything = self.getIncomingHeaders + self.getDataSentByAjaxCall
print(everything)
Do this:
def get(self):
print("%r %s" % (self.request, self.request.body.decode()))
For a "get" there is no request body, but you can put the same code in a "put" or "post" method and see the full request body along with headers, path, and so on.

POSTing to non-CGI scripts?

According to the http.server documentation BaseHTTPRequestHandler can handle POST requests.
class http.server.BaseHTTPRequestHandler(request, client_address,
server)¶ This class is used to handle the HTTP requests that arrive at
the server. By itself, it cannot respond to any actual HTTP requests;
it must be subclassed to handle each request method (e.g. GET or
POST). BaseHTTPRequestHandler provides a number of class and instance variables, and methods for use by subclasses.
However, down below it says:
do_POST() This method serves the 'POST' request type, only allowed for
CGI scripts. Error 501, “Can only POST to CGI scripts”, is output when
trying to POST to a non-CGI url.
What does this part of the documentation mean? Isn't that contradicting itself or am I misunderstanding something?
EDIT: To clarify, the following method I tried seems to work, I'd just like to know what the documentation of do_POST means.
from os import curdir
from os.path import join as pjoin
import requests
from http.server import BaseHTTPRequestHandler, HTTPServer
port = 18888
class StoreHandler(BaseHTTPRequestHandler):
store_path = pjoin(curdir, 'store.json')
def do_POST(self):
if self.path == '/store.json':
print("Got a connection from", self.client_address)
length = self.headers['content-length']
data = self.rfile.read(int(length))
print(data)
with open(self.store_path, 'w') as fh:
fh.write(data.decode())
self.send_response(200)
self.end_headers()
server = HTTPServer(('localhost', port), StoreHandler)
server.serve_forever()
CGIHTTPRequestHandler IS a subclass of SimpleHTTPRequestHandler, which is a subclass of BaseHTTPRequestHandler (I found this out by looking at the source code for SimpleHTTPServer.py and CGIHTTPServer.py). This part below:
do_POST() This method serves the 'POST' request type, only allowed for CGI scripts. Error 501, “Can only POST to CGI scripts”, is output when trying to POST to a non-CGI url.
Refers to CGIHTTPRequestHandler, not BaseHTTPRequestHandler! See:
http.server.BaseHTTPRequestHandler
CGIHTTPRequestHandler
do_POST() as documented is a method of CGIHTTPRequestHandler. Its default behavior does not affect BaseHTTPRequestHandler in any way.

Is it possible to use gzip compression with Server-Sent Events (SSE)?

I would like to know if it is possible to enable gzip compression
for Server-Sent Events (SSE ; Content-Type: text/event-stream).
It seems it is possible, according to this book:
http://chimera.labs.oreilly.com/books/1230000000545/ch16.html
But I can't find any example of SSE with gzip compression. I tried to
send gzipped messages with the response header field
Content-Encoding set to "gzip" without success.
For experimenting around SSE, I am testing a small web application
made in Python with the bottle framework + gevent ; I am just running
the bottle WSGI server:
#bottle.get('/data_stream')
def stream_data():
bottle.response.content_type = "text/event-stream"
bottle.response.add_header("Connection", "keep-alive")
bottle.response.add_header("Cache-Control", "no-cache")
bottle.response.add_header("Content-Encoding", "gzip")
while True:
# new_data is a gevent AsyncResult object,
# .get() just returns a data string when new
# data is available
data = new_data.get()
yield zlib.compress("data: %s\n\n" % data)
#yield "data: %s\n\n" % data
The code without compression (last line, commented) and without gzip
content-encoding header field works like a charm.
EDIT: thanks to the reply and to this other question: Python: Creating a streaming gzip'd file-like?, I managed to solve the problem:
#bottle.route("/stream")
def stream_data():
compressed_stream = zlib.compressobj()
bottle.response.content_type = "text/event-stream"
bottle.response.add_header("Connection", "keep-alive")
bottle.response.add_header("Cache-Control", "no-cache, must-revalidate")
bottle.response.add_header("Content-Encoding", "deflate")
bottle.response.add_header("Transfer-Encoding", "chunked")
while True:
data = new_data.get()
yield compressed_stream.compress("data: %s\n\n" % data)
yield compressed_stream.flush(zlib.Z_SYNC_FLUSH)
TL;DR: If the requests are not cached, you likely want to use zlib and declare Content-Encoding to be 'deflate'. That change alone should make your code work.
If you declare Content-Encoding to be gzip, you need to actually use gzip. They are based on the the same compression algorithm, but gzip has some extra framing. This works, for example:
import gzip
import StringIO
from bottle import response, route
#route('/')
def get_data():
response.add_header("Content-Encoding", "gzip")
s = StringIO.StringIO()
with gzip.GzipFile(fileobj=s, mode='w') as f:
f.write('Hello World')
return s.getvalue()
That only really makes sense if you use an actual file as a cache, though.
There's also middleware you can use so you don't need to worry about gzipping responses for each of your methods. Here's one I used recently.
https://code.google.com/p/ibkon-wsgi-gzip-middleware/
This is how I used it (I'm using bottle.py with the gevent server)
from gzip_middleware import Gzipper
import bottle
app = Gzipper(bottle.app())
run(app = app, host='0.0.0.0', port=8080, server='gevent')
For this particular library, you can set w/c types of responses you want to compress by modifying the DEFAULT_COMPRESSABLES variable for example
DEFAULT_COMPRESSABLES = set(['text/plain', 'text/html', 'text/css',
'application/json', 'application/x-javascript', 'text/xml',
'application/xml', 'application/xml+rss', 'text/javascript',
'image/gif'])
All responses go through the middleware and get gzipped without modifying your existing code. By default, it compresses responses whose content-type belongs to DEFAULT_COMPRESSABLES and whose content-length is greater than 200 characters.

Python read multiline post data

I'm using BaseHTTPRequestHandler to implement my httpserver. How do a I read a multiline post data in my do_PUT/do_POST?
Edit: I'm trying to implement a standalone script which sevices some custom requests, something like listener on a server, which consolidates/archives/extracts from various log files, I don't want implement something which requires a webserver, I don't have much experience in python, I would be grateful if someone could point any better solution.
Edit2: I can't use any external libraries/modules, I have to make do with plain vanilla python 2.4/java1.5/perl5.8.8, restrictive policies, my hands are tied
Getting the request body is as simple as reading from self.rfile, but you'll have to know how much to read if the client is using Connection: keep-alive. Something like this will work if the client specifies the Content-Length header...
from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
class RequestHandler(BaseHTTPRequestHandler):
def do_POST(self):
content_length = int(self.headers['Content-Length'])
post_data = self.rfile.read(content_length)
print post_data
server = HTTPServer(('', 8000), RequestHandler)
server.serve_forever()
...although it gets more complicated if the client sends data using chunked transfer encoding.

Getting Host field from HTTP request in BaseHTTPRequestHandler

I'm writing a script using BaseHTTPRequestHandler class. And in do_GET(self) method I need to get the content of the Host field from the HTTP request. I can do it by regexping the str(self.headers) like proposed here: Determine site domain in BaseHTTPServer, but it's kinda ugly and I wonder if there's cleaner way to do that.
The attribute self.headers is a dictionary-like structure, so you can do this:
def do_GET(self):
host = self.headers.get('Host')
When the header does not exist, None is returned.

Categories

Resources