Skip to content

Instantly share code, notes, and snippets.

@wecsam
Last active March 24, 2018 00:10
Show Gist options
  • Save wecsam/25e131f885c65f982d3fe2abef509660 to your computer and use it in GitHub Desktop.
Save wecsam/25e131f885c65f982d3fe2abef509660 to your computer and use it in GitHub Desktop.
Serves stuff from STDIN over HTTP
#!/usr/bin/env python3
import argparse, io, socketserver, sys, threading
class StringServer(socketserver.BaseRequestHandler):
# These headers will be sent with every HTTP reply.
HEADERS = (
b"HTTP/1.1 200 OK\r\n"
b"Content-Type: application/octet-stream\r\n"
)
# If True, then data that is received will be forwarded to STDOUT.
forward_to_stdout = False
# The value of this buffer will be sent in the body of every reply.
_body = io.BytesIO()
_body_protection = threading.Lock()
# We need a way to signal accumulate_forever to stop.
_accumulate_stop = threading.Event()
# Just send the request to STDOUT and then send the reply.
def handle(self):
request = self.request.recv(8192)
if self.forward_to_stdout:
sys.stdout.write(request.decode("UTF-8"))
with self._body_protection:
response = self._body.getvalue()
self.request.sendall(
self.HEADERS +
b"Content-Length: " + str(len(response)).encode("UTF-8") +
b"\r\n\r\n" +
response
)
# Start this method in a thread to move bytes from STDIN to the body buffer
# until accumulate_stop is called.
@classmethod
def accumulate_start(cls):
def accumulate_forever():
while not cls._accumulate_stop.is_set():
b = sys.stdin.readline().encode("UTF-8")
with cls._body_protection:
cls._body.write(b)
threading.Thread(
target=accumulate_forever,
name="Body Accumulator"
).start()
@classmethod
def accumulate_stop(cls):
cls._accumulate_stop.set()
sys.stderr.write("Press Enter to exit: ")
def serve(port, verbose=0):
StringServer.forward_to_stdout = verbose >= 2
try:
with socketserver.TCPServer(
("", port),
StringServer
) as server:
if verbose >= 1:
sys.stderr.write(
"Listening on 0.0.0.0:{}...\n".format(port)
)
sys.stderr.write("Press Ctrl+C at the end of the input.\n")
# Start another thread to read input from STDIN.
StringServer.accumulate_start()
# Start handling connections.
try:
server.serve_forever()
except KeyboardInterrupt:
# Ctrl+C was pressed.
sys.stderr.write(
"Caught termination signal. Server stopped.\n"
)
finally:
# Stop the thread that was started above.
StringServer.accumulate_stop()
except OSError as e:
sys.stderr.write("Error: {}\n".format(e))
def main():
# Parse the command-line arguments.
argparser = argparse.ArgumentParser(
description=
"This script serves everything from STDIN over HTTP. Just launch "
"it and then pipe or type some data into STDIN. Until Ctrl+C is "
"pressed, every TCP connection request to the specified port "
"will be answered with an HTTP response where the body contains "
"everything that has been received on STDIN so far. The data "
"that is received from the remote host in each TCP connection "
"is ignored."
)
argparser.add_argument(
"-p", "--port",
type=int,
default=8080,
help="the port on which to listen"
)
argparser.add_argument("-v", "--verbose", default=0, action="count")
arguments = argparser.parse_args()
# Create a socketserver to handle connections.
serve(arguments.port, arguments.verbose)
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment