Creating a Python webserver - layout and setup - python

am I going about this in the correct way? Ive never done anything like this before, so im not 100% sure on what I am doing. The code so far gets html and css files and that works fine, but images wont load, and will I have to create a new "if" for every different file type? or am I doing this a silly way...here is what I have:
import string,cgi,time
from os import curdir, sep
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
import os
import mimetypes
#import pri
port = 888
host = "0.0.0.0"
class MyHandler(BaseHTTPRequestHandler):
def do_GET(self):
try:
#RequestedURL = self.path
mimeType = mimetypes.guess_type(self.path)[0]
fileType = mimetypes.guess_extension(mimeType)
infoList = [mimeType, fileType]
if infoList[1] != ".py":
self.send_response(200)
self.send_header('Content-type', mimeType)
self.end_headers()
f = open(curdir + sep + self.path, "rb")
self.wfile.write(f.read())
f.close()
return
if fileType == ".py":
pythonFilename = self.path.lstrip("/")
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()
pyname = pythonFilename.replace("/", ".")[:-3]
print pythonFilename
print pyname
temp1 = pyname.split(".")
temp2 = temp1[-1]
print temp2
module = __import__(root.index)
self.wfile.write(module.root.index.do_work())
#module = __import__("test.index")
#self.wfile.write( module.index.do_work())
return
return
except IOError:
self.send_error(404,'File Not Found: %s' % self.path)
def do_POST(self):
global rootnode
try:
ctype, pdict = cgi.parse_header(self.headers.getheader('content-type'))
if ctype == 'multipart/form-data':
query=cgi.parse_multipart(self.rfile, pdict)
self.send_response(301)
self.end_headers()
upfilecontent = query.get('upfile')
print "filecontent", upfilecontent[0]
self.wfile.write("<HTML>POST OK.<BR><BR>");
self.wfile.write(upfilecontent[0]);
except :
pass
def main():
try:
server = HTTPServer((host, port), MyHandler)
print 'started httpserver:'
print ("Host: " + (host))
print ("Port: " + str(port))
server.serve_forever()
except KeyboardInterrupt:
print '^C received, shutting down server'
server.socket.close()
if __name__ == '__main__':
main()
html and css works, but png images do not load

You are on the right track with it, though your ifs are very redundant. I suggest you refactor the code to check for type using a loop and a dict:
mime = {"html":"text/html", "css":"text/css", "png":"image/png"}
if RequestedFileType in mime.keys():
self.send_response(200)
self.send_header('Content-type', mime[RequestedFileType])
self.end_headers()
print RequestedFileType
f = open(curdir + sep + self.path)
self.wfile.write(f.read())
f.close()
return
Also, you are sending binary files as text. Instead of open(curdir + sep + self.path) use open(curdir + sep + self.path, "b")
Gergely from toptal.com

As to a panoply of if statements, the usual approach is to have a file that handles the mapping between extensions and mime types (look here: List of ALL MimeTypes on the Planet, mapped to File Extensions?). Read that into an appropriate data structure.
You should probably be opening all files as binary unless they are a text/* mime type; for those you should ensure that your line endings are as specified in the appropriate RFC (if any - it's been years since I have needed to consult those standards, on account of not writing a web server for deployment in anger).
And a syntactical point:
>>> ('foo')
'foo'
>>> ('foo',)
('foo',)
>>>
Your brackets are redundant. You can index on the value you are extracting.

Related

How do I send Server Log to Client through http.server Python?

As the question, I now creating a simple http.server with Python and I have to send information about request like Client IP, login time of client, method GET/POST, HTTP header, HTTP body to Client and display it on Frontend. But I don't know how to send and how to get that information to send ? Please help me, thanks
This is my server code:
from http.server import BaseHTTPRequestHandler, HTTPServer
import os
import cgi
from supervisor.medusa.auth_handler import get_header
from database.db import create_table, signup, check_login
from random import randint
from datetime import datetime
import logging
islogin = False
def read_html_template(path):
try:
with open(path) as f:
file = f.read()
except Exception as e:
file = e
return file
class handler(BaseHTTPRequestHandler):
def gen_token(self):
return "".join(str(randint(1, 9)) for _ in range(25))
def get_Ip_addr(self):
return self.client_address[0]
def get_header(self):
return logging.error(self.headers)
def get_time(self):
now = datetime.now()
return now.strftime("%d/%m/%Y %H:%M:%S")
def data_to_send(self):
login_time = self.get_time()
header_data = self.get_header()
ip_client = self.get_Ip_addr()
data = ('{"IP_Client": '+ str(ip_client) + ', "HTTP_Header": ' + str(header_data) + ', "Login_Time": '+ str(login_time) + '}')
return data
def do_GET(self):
if self.path == '/':
self.path = './src/html/index.html'
elif self.path == '/login':
self.path = './src/html/Login.html'
elif self.path == '/signup':
self.path = './src/html/Signup.html'
elif self.path == '/forgotpwd':
self.path = './src/html/ForgotPwd.html'
elif self.path == '/home' and islogin == True:
self.path = './src/html/singnedIn.html'
try:
split_path = os.path.splitext(self.path)
request_extension = split_path[1]
if request_extension != ".py":
f = read_html_template(self.path)
self.send_response(200)
self.end_headers()
self.wfile.write(bytes(f, 'utf-8'))
else:
f = "File not found"
self.send_error(404,f)
except:
f = "File not found"
self.send_error(404,f)
with HTTPServer(('', 8000), handler) as server:
server.serve_forever()
My idea is write data to a json file and send it to client but 2 things I don't know is
How to send json file ? and
How to get data and write it to json file ?

my http server got post request including file, how to forward to external URL using POST method too

I have a problem, and I searched a lot but none of codes here do my request
I'm running python local server using http.server - BaseHTTPRequestHandler
here is my code
def run_http():
port=80
server_address = ('', port)
httpd = ThreadingHTTPServer(server_address, serverClass)
# print('HTTP server running on port %s'% port)
httpd.serve_forever()
class serverClass(BaseHTTPRequestHandler):
def _set_headers(self):
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()
def do_GET(self):
self._set_headers()
if self.path == '/':
with open("static\\index.html", 'r') as f:
res = f.read()
self.wfile.write(res.encode('utf-8'))
elif self.path == '/favicon.ico':
with open("static\\favicon.ico", 'rb') as f:
res = f.read()
self.wfile.write(res)
else:
...
def do_HEAD(self):
self._set_headers()
def do_POST(self):
# content_length = int(self.headers['Content-Length'])
# post_data = self.rfile.read(content_length)
# <= here I need to forward the post request to another URL _POST_URL
# The post request I received may include file and may be not, so I need to forward the post request as it is
I tried some codes for example:
ctype, pdict = cgi.parse_header(self.headers['content-type'])
pdict['boundary'] = bytes(pdict['boundary'], "utf-8")
if ctype == 'multipart/form-data':
fields = cgi.parse_multipart(self.rfile, pdict)
field_file = {'file':(fields.get('file'), "multipart/form-data")}
fields.pop('file')
r = requests.post(_POST_URL+'/in.php', files = field_file, data = fields)
note: I know that the file will be with post name file
That code works, but the problem is I don't sent the file type or the file name, and I cannot got it really.
Am I in the right way, or I should use more efficient codes
Ok I used cgi.FieldStorage
and here is the code if somebody may interesed;
def do_POST(self):
form = cgi.FieldStorage( fp=self.rfile, headers=self.headers, environ={'REQUEST_METHOD':'POST', 'CONTENT_TYPE':self.headers['Content-Type'] })
post_data = {}
for field in form:
if field != 'file':
post_data[field] = form.getvalue(field)
# print(post_data)
try:
fileitem = form['file']
if fileitem.filename:
fn = os.path.basename(fileitem.filename)
open('./tmp/' + fn, 'wb').write(fileitem.file.read())
data_files = {'file' : open('./tmp/' + fn, 'rb')}
# print(data_files)
r = requests.post(_POST_URL, data = post_data, files = data_files)
except:
r = requests.post(_POST_URL, data = post_data)
res = r.text
try:
data_files = None
os.remove('./tmp/' + fn)
except:
pass
self._set_headers()
self.wfile.write(res.encode('utf-8'))
This code is working for me, I don't know if there is a better way to do that!

http webserver simply stops responding

Earlier I wrote multi threaded web server, which at times would simply stop processing requests and also getting terminated at peak times.
I've implemented same opencv based processing in Python Websocket based server too which is working fine.
For very old browsers, I also need POST based processing using web server. I converted from multithreading to single but that also is stopping different times and not printing any log etc.
I checked syslog but not clue. More than a week has gone by without finding a solution. I suspect something related to Digital Ocean VPS or network.
I've this code and can't figure why it should stop responding:
from http.server import HTTPServer, BaseHTTPRequestHandler
import threading
import cgi
import tempfile
import resource
import base64
from common import *
from datetime import datetime
print( datetime.now());
gg_hashmap = getHash()
USE_HTTPS = True
def dump(obj):
for attr in dir(obj):
print("obj.%s = %r" % (attr, getattr(obj, attr)))
class PostHandler(BaseHTTPRequestHandler):
def handle(self):
try:
BaseHTTPRequestHandler.handle(self)
except :
pass
def do_POST(self):
try:
print("new req="+str( datetime.now()),flush=True);
form = cgi.FieldStorage(
fp=self.rfile,
headers=self.headers,
environ={'REQUEST_METHOD': 'POST',
'CONTENT_TYPE': self.headers['Content-Type'],
})
self.send_response(200)
self.send_header("Content-type", "text/html")
self.send_header("Access-Control-Allow-Origin", "*")
self.end_headers()
for field in form.keys():
field_item = form[field]
if field_item.filename:
file_data = field_item.file.read()
file_len = len(file_data)
del file_data
self.wfile.write('\tUploaded %s as "%s" (%d bytes)\n' % \
(field, field_item.filename, file_len))
else:
pass
if ('base64' in form and 'license' in form):
print("license=",form['license'].value);
global gg_hashmap
file_content = form['base64'].value
try:
f, temp_file_path = tempfile.mkstemp(prefix='sand', suffix='jpg')
os.close(f)
with open(temp_file_path, 'wb') as w:
w.write(base64.b64decode (file_content))
input_hashes = get_input_img(temp_file_path)
all_letters = ""
if input_hashes != None:
for inp_hash in input_hashes:
lowest = 1000
lowest_letter = ''
for letter, arr in gg_hashmap.items():
for hashval in arr:
if int(inp_hash - hashval) < lowest:
lowest = int(inp_hash - hashval)
lowest_letter = letter
all_letters += lowest_letter
self.wfile.write(bytes(all_letters, "utf8"))
except Exception as e:
print("exception3 caught")
print(e)
print(str(e))
return
except Exception as e:
print("Caught unknown exception",e)
def do_GET(self):
self.send_response(200)
self.end_headers()
message = threading.currentThread().getName()
self.wfile.write(bytes(message,'utf-8'))
self.wfile.write('\n')
return
form = cgi.FieldStorage(
fp=self.rfile,
headers=self.headers,
environ={'REQUEST_METHOD': 'POST',
'CONTENT_TYPE': self.headers['Content-Type'],
})
self.send_response(200)
self.end_headers()
for field in form.keys():
field_item = form[field]
if field_item.filename:
file_data = field_item.file.read()
file_len = len(file_data)
del file_data
self.wfile.write('\tUploaded %s as "%s" (%d bytes)\n' % \
(field, field_item.filename, file_len))
else:
pass
return
def run():
# resource.setrlimit(resource.RLIMIT_STACK, (2**29,-1))
# threading.stack_size(24*1048576)
server = HTTPServer(('0.0.0.0', 443), PostHandler)
if USE_HTTPS:
import ssl
server.socket = ssl.wrap_socket(server.socket, keyfile='./ssl/key.pem', certfile='./ssl/public.pem'
, ca_certs="./ssl/cap1_transactionfailed_com.ca-bundle" , server_side=True)
server.serve_forever()
if __name__ == '__main__':
run()
I don't think much anyone will want to read through all 157 lines of convoluted HTTP request handling code (of which some isn't even posted, from common import *) to try and decipher why it might stop at some given time.
It's likely not the answer you want to hear, but HTTPServer really isn't what anyone uses in production for Python.
You should look into rewriting your code with either (my recommendations at the time of writing)
FastAPI (or its underlying Starlette framework) on Uvicorn (Uvicorn will let you do the websocket stuff in the same process), or
Flask on Gunicorn or uWSGI
For instance, here's a rough estimation of what your code would look like with Starlette. (There may be bugs since it's dry-coded, and it's certainly not fully async, but that doesn't matter here.)
import tempfile
import base64
from starlette.applications import Starlette
from starlette.requests import Request
from starlette.responses import PlainTextResponse
app = Starlette()
def get_all_letters(input_hashes):
all_letters = ""
if input_hashes:
for inp_hash in input_hashes:
lowest = 1000
lowest_letter = ""
for letter, arr in gg_hashmap.items():
for hashval in arr:
if int(inp_hash - hashval) < lowest:
lowest = int(inp_hash - hashval)
lowest_letter = letter
all_letters += lowest_letter
return all_letters
#app.route("/", methods=["GET", "POST"])
async def handle(request: Request):
if request.method == "GET":
return PlainTextResponse("Hello!")
form = await request.form()
if not ("base64" in form and "license" in form):
return PlainTextResponse("Missing data!", status_code=400)
with tempfile.NamedTemporaryFile(prefix="sand", suffix="jpg") as f:
content = await form["base64"].read()
f.write(base64.b64decode(content))
f.flush()
input_hashes = get_input_img(f)
if not input_hashes:
return PlainTextResponse("No input hashes!", status_code=400)
all_letters = get_all_letters(input_hashes)
return PlainTextResponse(all_letters)
You could then run this using Uvicorn (which will also handle all of that HTTPS stuff for you).
With mkstemp you must delete the tempfile. You probably run out of disk space or max out files in temp directory. As AKX mentioned though you should look into using a more robust http server. if the file thing isn't your problem there are numerous other issues that could come up when using a non production HTTP server.

python http server threading via cli

New in version 3.7 supports ThreadingHTTPServer as mentioned in doc
to run from command line we use
python -m http.server
but its still run normal HTTPServer, is there any way to enable via command line.
EDITED:
python 3.7 runs ThreadingHTTPServer by default, no argument necessary
Simple Python 2 HTTP Server with multi-threading and partial-content support
#!/usr/bin/env python2
# Standard library imports.
from SocketServer import ThreadingMixIn
import BaseHTTPServer
import SimpleHTTPServer
import sys
import json
import os
from os.path import (join, exists, dirname, abspath, isabs, sep, walk, splitext,
isdir, basename, expanduser, split, splitdrive)
from os import makedirs, unlink, getcwd, chdir, curdir, pardir, rename, fstat
from shutil import copyfileobj, copytree
import glob
from zipfile import ZipFile
from urlparse import urlparse, parse_qs
from urllib import urlopen, quote, unquote
from posixpath import normpath
from cStringIO import StringIO
import re
import ConfigParser
import cgi
import threading
import socket
import errno
DATA_DIR = getcwd() # join(expanduser('~'), APP_NAME)
class ThreadingHTTPServer(ThreadingMixIn, BaseHTTPServer.HTTPServer):
pass
class RequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
""" Handler to handle POST requests for actions.
"""
serve_path = DATA_DIR
def do_GET(self):
""" Overridden to handle HTTP Range requests. """
self.range_from, self.range_to = self._get_range_header()
if self.range_from is None:
# nothing to do here
return SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
print 'range request', self.range_from, self.range_to
f = self.send_range_head()
if f:
self.copy_file_range(f, self.wfile)
f.close()
def copy_file_range(self, in_file, out_file):
""" Copy only the range in self.range_from/to. """
in_file.seek(self.range_from)
# Add 1 because the range is inclusive
bytes_to_copy = 1 + self.range_to - self.range_from
buf_length = 64*1024
bytes_copied = 0
while bytes_copied < bytes_to_copy:
read_buf = in_file.read(min(buf_length, bytes_to_copy-bytes_copied))
if len(read_buf) == 0:
break
out_file.write(read_buf)
bytes_copied += len(read_buf)
return bytes_copied
def send_range_head(self):
"""Common code for GET and HEAD commands.
This sends the response code and MIME headers.
Return value is either a file object (which has to be copied
to the outputfile by the caller unless the command was HEAD,
and must be closed by the caller under all circumstances), or
None, in which case the caller has nothing further to do.
"""
path = self.translate_path(self.path)
f = None
if isdir(path):
if not self.path.endswith('/'):
# redirect browser - doing basically what apache does
self.send_response(301)
self.send_header("Location", self.path + "/")
self.end_headers()
return None
for index in "index.html", "index.htm":
index = join(path, index)
if exists(index):
path = index
break
else:
return self.list_directory(path)
if not exists(path) and path.endswith('/data'):
# FIXME: Handle grits-like query with /data appended to path
# stupid grits
if exists(path[:-5]):
path = path[:-5]
ctype = self.guess_type(path)
try:
# Always read in binary mode. Opening files in text mode may cause
# newline translations, making the actual size of the content
# transmitted *less* than the content-length!
f = open(path, 'rb')
except IOError:
self.send_error(404, "File not found")
return None
if self.range_from is None:
self.send_response(200)
else:
self.send_response(206)
self.send_header("Content-type", ctype)
fs = fstat(f.fileno())
file_size = fs.st_size
if self.range_from is not None:
if self.range_to is None or self.range_to >= file_size:
self.range_to = file_size-1
self.send_header("Content-Range",
"bytes %d-%d/%d" % (self.range_from,
self.range_to,
file_size))
# Add 1 because ranges are inclusive
self.send_header("Content-Length",
(1 + self.range_to - self.range_from))
else:
self.send_header("Content-Length", str(file_size))
self.send_header("Last-Modified", self.date_time_string(fs.st_mtime))
self.end_headers()
return f
def list_directory(self, path):
"""Helper to produce a directory listing (absent index.html).
Return value is either a file object, or None (indicating an
error). In either case, the headers are sent, making the
interface the same as for send_head().
"""
try:
list = os.listdir(path)
except os.error:
self.send_error(404, "No permission to list directory")
return None
list.sort(key=lambda a: a.lower())
f = StringIO()
displaypath = cgi.escape(unquote(self.path))
f.write('<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">')
f.write("<html>\n<title>Directory listing for %s</title>\n" % displaypath)
f.write("<body>\n<h2>Directory listing for %s</h2>\n" % displaypath)
f.write("<hr>\n<ul>\n")
for name in list:
fullname = os.path.join(path, name)
displayname = linkname = name
# Append / for directories or # for symbolic links
if os.path.isdir(fullname):
displayname = name + "/"
linkname = name + "/"
if os.path.islink(fullname):
displayname = name + "#"
# Note: a link to a directory displays with # and links with /
f.write('<li>%s\n'
% (quote(linkname), cgi.escape(displayname)))
f.write("</ul>\n<hr>\n</body>\n</html>\n")
length = f.tell()
f.seek(0)
self.send_response(200)
encoding = sys.getfilesystemencoding()
self.send_header("Content-type", "text/html; charset=%s" % encoding)
self.send_header("Content-Length", str(length))
self.end_headers()
return f
def translate_path(self, path):
""" Override to handle redirects.
"""
path = path.split('?',1)[0]
path = path.split('#',1)[0]
path = normpath(unquote(path))
words = path.split('/')
words = filter(None, words)
path = self.serve_path
for word in words:
drive, word = splitdrive(word)
head, word = split(word)
if word in (curdir, pardir): continue
path = join(path, word)
return path
# Private interface ######################################################
def _get_range_header(self):
""" Returns request Range start and end if specified.
If Range header is not specified returns (None, None)
"""
range_header = self.headers.getheader("Range")
if range_header is None:
return (None, None)
if not range_header.startswith("bytes="):
print "Not implemented: parsing header Range: %s" % range_header
return (None, None)
regex = re.compile(r"^bytes=(\d+)\-(\d+)?")
rangething = regex.search(range_header)
if rangething:
from_val = int(rangething.group(1))
if rangething.group(2) is not None:
return (from_val, int(rangething.group(2)))
else:
return (from_val, None)
else:
print 'CANNOT PARSE RANGE HEADER:', range_header
return (None, None)
def get_server(port=8000, next_attempts=0, serve_path=None):
Handler = RequestHandler
if serve_path:
Handler.serve_path = serve_path
while next_attempts >= 0:
try:
httpd = ThreadingHTTPServer(("", port), Handler)
return httpd
except socket.error as e:
if e.errno == errno.EADDRINUSE:
next_attempts -= 1
port += 1
else:
raise
def main(args=None):
if args is None:
args = sys.argv[1:]
PORT = 8000
if len(args)>0:
PORT = int(args[-1])
serve_path = DATA_DIR
if len(args) > 1:
serve_path = abspath(args[-2])
httpd = get_server(port=PORT, serve_path=serve_path)
print "serving at port", PORT
httpd.serve_forever()
if __name__ == "__main__" :
main()

http.server if statement os.path.isfile() doesn't work

So this is my code:
from http.server import BaseHTTPRequestHandler, HTTPServer
import requests, json, os
PORT = 1337
class getHandler(BaseHTTPRequestHandler):
def handleJSON(self, provider, data):
if provider == "provider_1":
json_data = json.loads(data)
sl_token = json_data["access_token"]
return sl_token
elif provider == "provider_2":
json_data = json.loads(data)
pb_token = json_data["access_token"]
return pb_token
def do_GET(self):
data = self.requestline
self.send_response(200)
self.send_header('Content-type','text/html')
self.end_headers()
self.wfile.write(b'You may close the tab now')
print("Raw Data: " + data)
if not os.path.isfile("PbToken.txt") and os.path.isfile("SlToken.txt"):
if "GET /?code=" and "&state=" in data: # Provider_1
print("Provider_1 Data:", data)
pb_code = data[data.find("/?code=") + len("/?code="):data.find("&state=")]
with open("PbToken.txt", "w") as file:
file.write(pb_code)
file.close()
elif "GET /?code=" in data: # Provider_2
print("Provider_2 Data:", data)
sl_code = data.strip()
sl_code = sl_code[sl_code.rindex("/?code=") + len("/?code="):sl_code.rindex(" ")]
with open("SlToken.txt", "w") as file:
file.write(sl_code)
file.close()
else:
raise SystemExit
server = HTTPServer(('localhost', PORT), getHandler)
print("Started server on port", PORT)
server.serve_forever()
So from the class getHandler in the function do_GET(self) it never makes it past the if not os.path.isfile("PbToken.txt") and os.path.isfile("SlToken.txt"): statement (I've of course made sure the files aren't actually there). I want it to check if both of the files exist, if they don't do what's written below. If the files exist they should go straight to the else statement where it uses raise SystemExit. What am I doing wrong?
It should've been or not and...
So simply if not os.path.isfile("PbToken.txt") or not os.path.isfile("SlToken.txt"): to see if one of the files are missing.

Categories