I'm running the code below to get POST messages. How do I get the file (sent with POST) and how do I respond with HTTP status code 200 OK?
#!/usr/bin/env python
import ssl
import BaseHTTPServer, SimpleHTTPServer
from BaseHTTPServer import BaseHTTPRequestHandler
class HttpHandler(BaseHTTPRequestHandler):
def do_POST(self):
print "got POST message"
# how do I get the file here?
print self.request.FILES
httpd = BaseHTTPServer.HTTPServer(('localhost', 4443), HttpHandler)
httpd.socket = ssl.wrap_socket(httpd.socket, certfile='./server.pem', server_side=True)
httpd.serve_forever()
$curl -X POST -d #../some.file https://localhost:4443/resource --insecure
AttributeError: 'SSLSocket' object has no attribute 'FILES'
BaseHTTPRequestHandler will process the first line and the headers of the http request then leave the rest up to you.
You need to read the remainder of the request using BaseHTTPRequestHandler.rfile
You can use self.send_response(200) to respond with 200 OK
Given the curl command you have specified the following should answer your question:
class HttpHandler(BaseHTTPRequestHandler):
def do_POST(self):
content_length = int(self.headers['Content-Length'])
file_content = self.rfile.read(content_length)
# Do what you wish with file_content
print file_content
# Respond with 200 OK
self.send_response(200)
Note that by using -d #../some.file in your curl command you are saying "this is an ascii file, oh and strip out the newlines and carriage returns please", thus there could be differences between the file you are using and the data you get in the request. Your curl command does not emulate an html form file-upload like post request.
It is generally available in
request.FILES
dictionary
Related
I'm using BaseHTTPServer.BaseHTTPRequestHandler in order to implement my server.
currently I repsonse to get request with merely binary data file.
self.send_response(200)
self.send_header("Content-Type", 'application/octet-stream')
self.send_header("Content-Disposition", 'attachment; filename="{}"'.format(os.path.basename(FILEPATH)))
fs = os.fstat(f.fileno())
self.send_header("Content-Length", str(fs.st_size))
self.end_headers()
Now it's requested to add another section which include some short json formatted configuration data (i.e. {'status': 'ok', 'type': 'keepalive'}) and i'd rather pass this information on the same response separated by unique http header or by the http body.
What is the best way to do so ? I'd like to know how to extend my code to support this.
Thanks
There's lots of ways to do this, I think the best choice is going to depend on what your receiving side is capable of understanding most easily.
The most literal interpretation would be to use content-type multipart/mixed https://www.w3.org/Protocols/rfc1341/7_2_Multipart.html but you're probably going to have to write your own parsing on the receiving end. I don't know if this is exactly to spec, but it gets the idea across:
from http.server import BaseHTTPRequestHandler
import http.server
import socketserver
import string
import random
import io
PORT = 8000
class ResponsePart:
def __init__(self, content, content_type):
self.content = content.encode('utf-8')
self.content_type = content_type.encode('utf-8')
class Mine(http.server.BaseHTTPRequestHandler):
def get_separator(self, parts):
while True:
boundary = []
for i in range(32):
boundary.append(random.choice(string.digits + string.ascii_letters))
boundary = ''.join(boundary).encode('ascii')
for part in parts:
if boundary in part:
break
else:
return boundary
def do_GET(self):
responses = [
ResponsePart('abc123', 'Content-type: application/octet-stream'),
ResponsePart('{"a":"b"}', 'Content-type: application/json'),
]
boundary = self.get_separator([r.content for r in responses])
self.send_response(200)
self.send_header("Content-Type", 'multipart/mixed; boundary=' + boundary.decode('ascii'))
self.end_headers()
for piece in responses:
self.wfile.write(b'--')
self.wfile.write(boundary)
self.wfile.write(b'\r\n')
self.wfile.write(piece.content_type)
self.wfile.write(b'\r\n')
self.wfile.write(piece.content)
self.wfile.write(b'\r\n')
Handler = Mine
with socketserver.TCPServer(("", PORT), Handler) as httpd:
httpd.serve_forever()
With that out of the way, I'd do this probably using JSON or something so that you're returning a single consistent content-type:
from http.server import BaseHTTPRequestHandler
import http.server
import socketserver
import string
import random
import io
import json
PORT = 8000
class Mine(http.server.BaseHTTPRequestHandler):
def do_GET(self):
response = {
'filedata': 'abc123',
'status': {"a":"b"},
}
output_data = json.dumps(response).encode('utf-8')
self.send_response(200)
self.send_header("Content-Type", 'application/octet-stream')
self.send_header("Content-Length", str(len(output_data)))
self.end_headers()
self.wfile.write(output_data)
Handler = Mine
with socketserver.TCPServer(("", PORT), Handler) as httpd:
httpd.serve_forever()
This is going to be far easier to handle on the receiving end, one json decode and you're done.
I have a Python class that inherits BaseHTTPRequestHandler and implements the method do_POST.
I currently only succeed to respond with an integer status, e.g. 200, using the following command at the end of the method:
self.send_response(200)
I am trying to also send some string as a part of the response. How should I do it?
At least in my environment (Python 3.7) i have to use
self.send_response(200)
self.send_header('Content-Type', 'application/json')
self.end_headers()
self.wfile.write(json_str.encode(encoding='utf_8'))
otherwise this error will be thrown:
TypeError: a bytes-like object is required, not 'str'
It turns out to be pretty simple, though there aren't many examples for it.
Just use:
self.wfile.write(YOUR_STRING_HERE)
Specifically for the case of json:
import json
json_string = json.dumps(YOUR_DATA_STRUCTURE_TO_CONVERT_TO_JSON)
self.wfile.write(json_string)
It's an old question. Still, if someone else might be wondering the same, here's my 2 cent.
If you are doing anything useful, apart from playing around with python, you should start looking for standard python frameworks to handle HTTP server operations, like Django or Flask.
That being said, there's a small stub that I use to act as a test server for my outgoing requests, which should answer your question. You can set any status code, header or response body by modifying it:
#!/usr/bin/env python
# Reflects the requests with dummy responses from HTTP methods GET, POST, PUT, and DELETE
# Written by Tushar Dwivedi (2017)
import json
from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
from optparse import OptionParser
class RequestHandler(BaseHTTPRequestHandler):
def do_GET(self):
request_path = self.path
print("\n----- Request Start ----->\n")
print("request_path :", request_path)
print("self.headers :", self.headers)
print("<----- Request End -----\n")
self.send_response(200)
self.send_header("Set-Cookie", "foo=bar")
self.end_headers()
self.wfile.write(json.dumps({'hello': 'world', 'received': 'ok'}))
def do_POST(self):
request_path = self.path
# print("\n----- Request Start ----->\n")
print("request_path : %s", request_path)
request_headers = self.headers
content_length = request_headers.getheaders('content-length')
length = int(content_length[0]) if content_length else 0
# print("length :", length)
print("request_headers : %s" % request_headers)
print("content : %s" % self.rfile.read(length))
# print("<----- Request End -----\n")
self.send_response(200)
self.send_header("Set-Cookie", "foo=bar")
self.end_headers()
self.wfile.write(json.dumps({'hello': 'world', 'received': 'ok'}))
do_PUT = do_POST
do_DELETE = do_GET
def main():
port = 8082
print('Listening on localhost:%s' % port)
server = HTTPServer(('', port), RequestHandler)
server.serve_forever()
if __name__ == "__main__":
parser = OptionParser()
parser.usage = ("Creates an http-server that will echo out any GET or POST parameters, and respond with dummy data\n"
"Run:\n\n")
(options, args) = parser.parse_args()
main()
Again, even if you are just learning, and you even need to add 5-6 of if elses to the above to do what you are doing, it's better to do things right from the beginning, to avoid a lot of rework in future. Use a framework capable of handling boilerplate stuff for you.
Flask code -
#app.route('/messages', methods = ['POST'])
def api_message():
if request.headers['Content-Type'] == 'text/plain':
return "Text Message: " + request.data
elif request.headers['Content-Type'] == 'application/json':
f = open(filename,'r')
l = f.readlines()
f.close()
return len(l)
On running, I get error as -
curl -H "Content-Type:application/json" -X POST http://127.0.0.1:5000/messages --data filename=#hello.json
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>500 Internal Server Error</title>
<h1>Internal Server Error</h1>
<p>The server encountered an internal error and was unable to complete your request. Either the server is overloaded or there is an error in the application.</p>
Am I accessing the curl param wrong (filename)? Or I am sending the file in wrong way?
Also Upload a file to a python flask server using curl
Tried doing
f = request.files['filename']
Still, same error.
What your curl command code is doing is reading the file hello.json and putting it in the body of the request. (This feature is actually very useful if you have a large chunk of JSON you need to send to the server).
Normally in application/json requests you send the JSON as the body of the request, so this may be what you want. You can use request.get_json to get this data as a Python dictionary.
If you want to upload an actual file - like uploading a picture - you want multi part form encoding, which you tell curl to send via the -F parameter. (See also: an SO answer about this: https://stackoverflow.com/a/12667839/224334 ).
I wrote a HTTP server using python, but I do not know how to get the HTTP body. what should I do to get the HTTP body?
here is my code:
from http.server import HTTPServer,BaseHTTPRequestHandler
class MyHTTPHandler(BaseHTTPRequestHandler):
def do_GET(self):
print("connect from ",self.client_address)
print(self.headers)
length = self.headers['Content-Length']
print(length)
addr = ('',21567)
server = HTTPServer(addr,MyHTTPHandler)
server.serve_forever()
Having a request body in a GET request is not a good practice, as its discussed here: HTTP GET with request body
Instead, you can change your method to POST and check there the BaseHTTPRequestHandler documentation: https://docs.python.org/2/library/basehttpserver.html
Especially this part:
rfile
Contains an input stream, positioned at the start of the optional input data.
I'm having a difficulty trying to make a Python REST POST to a webservice running on Glassfish. I have verified that POST works ok using CURL but having no luck with Python.
Here is the CURL request that works ok.
curl -X POST -H "Content-Type: application/json" -d '{"id":1,"lastname":"smith"}'
http://192.168.0.20:8080/field1/resources/com.field1entity.field1
Here is the Python code to make the POST request
import urllib
import httplib2
def call():
http = httplib2.Http()
url = 'http://192.168.0.20:8080/field1/resources/com.field1entity.field1'
params = urllib.urlencode({"id":11111,"lastname":"oojamalip"})
response, content = http.request(url, 'POST', params, headers={'Content-type':'application/json'})
print "lets stop here to have a looksy at the variables"
print content
if __name__ == '__main__':
namesPage = call()
print namesPage
Output from console,
Unexpected character ('l' (code 108)): expected a valid value (number, String, array, object, 'true', 'false' or 'null')
at [Source: org.apache.catalina.connector.CoyoteInputStream#18f494d; line: 1, column: 2]
Hope someone can shed some light on the problem.
thanks
Nick
You are url encoding the prams and then telling the server it is json encoded
import json
params = json.dumps({"id":11111,"lastname":"oojamalip"})
# then
response, content = http.request(url, 'POST', body=params, headers={'Content-type':'application/json'})