Skip to content

Commit

Permalink
add python server
Browse files Browse the repository at this point in the history
  • Loading branch information
LeiYangGH committed Jun 24, 2021
1 parent ee3d3f7 commit d2d4379
Show file tree
Hide file tree
Showing 2 changed files with 263 additions and 0 deletions.
117 changes: 117 additions & 0 deletions python-server/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.pytest_cache/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
target/

# Jupyter Notebook
.ipynb_checkpoints

# IPython
profile_default/
ipython_config.py

# pyenv
.python-version

# celery beat schedule file
celerybeat-schedule

# SageMath parsed files
*.sage.py

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# Pyre type checker
.pyre/
.idea/
*.opus
146 changes: 146 additions & 0 deletions python-server/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
'''
chunked_server_test.py
Copyright August 3, 2012
Released into the public domain
This implements a chunked server using Python threads and the built-in
BaseHTTPServer module. Enable gzip compression at your own peril - web
browsers seem to have issues, though wget, curl, Python's urllib2, my own
async_http library, and other command-line tools have no problems.
'''

from http.server import BaseHTTPRequestHandler, HTTPServer
import gzip
from socketserver import ThreadingMixIn
import time


class ChunkingHTTPServer(ThreadingMixIn,
HTTPServer):
'''
This is just a proof of concept server that uses threads. You can make it
fork, maybe hack up a worker thread model, or even use multiprocessing.
That's your business. But as-is, it works reasonably well for streaming
chunked data from a server.
'''
daemon_threads = True


class ListBuffer(object):
'''
This little bit of code is meant to act as a buffer between the optional
gzip writer and the actual outgoing socket - letting us properly construct
the chunked output. It also lets us quickly and easily determine whether
we need to flush gzip in the case where a user has specified
'ALWAYS_SEND_SOME'.
This offers a minimal interface necessary to back a writing gzip stream.
'''

__slots__ = 'buffer',

def __init__(self):
self.buffer = []

def __nonzero__(self):
return len(self.buffer)

def write(self, data):
if data:
self.buffer.append(data)

def flush(self):
pass

def getvalue(self):
data = ''.join(self.buffer)
self.buffer = []
return data


class ChunkingRequestHandler(BaseHTTPRequestHandler):
'''
Nothing is terribly magical about this code, the only thing that you need
to really do is tell the client that you're going to be using a chunked
transfer encoding.
Gzip compression works partially. See the module notes for more
information.
'''
ALWAYS_SEND_SOME = False
ALLOW_GZIP = False
protocol_version = 'HTTP/1.1'

def do_GET(self):
ae = self.headers.get('accept-encoding') or ''
use_gzip = 'gzip' in ae and self.ALLOW_GZIP

# send some headers
self.send_response(200)
self.send_header('Transfer-Encoding', 'chunked')
self.send_header('Content-type', 'text/plain')

# use gzip as requested
if use_gzip:
self.send_header('Content-Encoding', 'gzip')
buffer = ListBuffer()
output = gzip.GzipFile(mode='wb', fileobj=buffer)

self.end_headers()

def write_chunk():
tosend = '%X\r\n%s\r\n' % (len(chunk), chunk)
bytes = tosend.encode()
self.wfile.write(bytes)

# get some chunks
for chunk in chunk_generator():
if not chunk:
continue

# we've got to compress the chunk
if use_gzip:
output.write(chunk)
# we'll force some output from gzip if necessary
if self.ALWAYS_SEND_SOME and not buffer:
output.flush()
chunk = buffer.getvalue()

# not forced, and gzip isn't ready to produce
if not chunk:
continue

write_chunk()

# no more chunks!

if use_gzip:
# force the ending of the gzip stream
output.close()
chunk = buffer.getvalue()
if chunk:
write_chunk()

# send the chunked trailer
self.wfile.write('0\r\n\r\n'.encode())


def chunk_generator():
chunk_strings = [
'a' * 10,
'b' * 100,
'c' * 1000,
'd' * 10000,
]
for chunk_str in chunk_strings:
time.sleep(.1)
yield chunk_str


if __name__ == '__main__':
server = ChunkingHTTPServer(
('0.0.0.0', 8080), ChunkingRequestHandler)
print('Starting server, use <Ctrl-C> to stop')
server.serve_forever()

0 comments on commit d2d4379

Please sign in to comment.