Initial commit, just gotta fix README
This commit is contained in:
commit
89a5fe9824
|
@ -0,0 +1,2 @@
|
||||||
|
*.pyc
|
||||||
|
__pycache__/*
|
|
@ -0,0 +1,3 @@
|
||||||
|
.PHONY: test
|
||||||
|
test:
|
||||||
|
sudo ncat -l 80 -k --sh-exec "./serve"
|
|
@ -0,0 +1,3 @@
|
||||||
|
# PyServ - Python server (use with `ncat` or similar)
|
||||||
|
|
||||||
|
A Python framework for HTTP servers. Requires ncat or similar.
|
|
@ -0,0 +1,5 @@
|
||||||
|
# MineRobber's blog generator
|
||||||
|
Made with Python, love, care, and [markdown2](https://github.com/trentm/python-markdown2)!
|
||||||
|
|
||||||
|
##Configuration
|
||||||
|
It's all configured already.
|
|
@ -0,0 +1,66 @@
|
||||||
|
import cPickle, subprocess, tempfile, markdown2, config, time, argparse, tempfile, os;
|
||||||
|
#print("MineRobber's blog generator")
|
||||||
|
#print("------------------------")
|
||||||
|
try:
|
||||||
|
blog_posts = cPickle.load(open("posts.pickled","rb"))
|
||||||
|
except:
|
||||||
|
blog_posts = []
|
||||||
|
parser = argparse.ArgumentParser(description="A blog generator.")
|
||||||
|
parser.add_argument("--workflow",help="Run the basic workflow (write a new post, generate the page, and quit)",action="store_true")
|
||||||
|
parser.add_argument("--generate",help="Generate the page and quit",action="store_true")
|
||||||
|
args = parser.parse_args()
|
||||||
|
if not args.workflow and not args.generate:
|
||||||
|
print("MineRobber's blog generator")
|
||||||
|
print("------------------------")
|
||||||
|
print("'n' for new post, 'g' to generate, 'q' to quit.")
|
||||||
|
running = True;
|
||||||
|
def tweetlink():
|
||||||
|
return config.tweetlink_do and config.tweetlink_location and config.tweetlink_url
|
||||||
|
def loop(choice):
|
||||||
|
if choice.lower() == "n":
|
||||||
|
title = raw_input("Title: ")
|
||||||
|
bodyf = tempfile.NamedTemporaryFile()
|
||||||
|
os.system("nano {}".format(bodyf.name))
|
||||||
|
body = markdown2.markdown(bodyf.read().rstrip())
|
||||||
|
bodyf.close()
|
||||||
|
blog_posts.append([title, body])
|
||||||
|
elif choice.lower() == "g":
|
||||||
|
rss = "<?xml version='1.0' encoding='UTF-8'?><rss version='2.0'><channel><title>"+config.title+"</title><link>"+config.url+"</link><description>"+config.desc+"</description>"
|
||||||
|
contents = "<html><head><title>{0}</title><link rel='stylesheet' href='{1}'></head><body><h1>{0}</h1>".format(config.title,config.css_location)
|
||||||
|
blog_posts.reverse()
|
||||||
|
i = len(blog_posts)
|
||||||
|
for post in blog_posts:
|
||||||
|
contents += "<h2 id='post"+str(i)+"'>"+post[0]+"</h2>"+post[1]
|
||||||
|
if tweetlink():
|
||||||
|
contents += "<p><a href='{}'>Tweet this</a></p>".format(config.tweetlink_url+"#post"+str(i))
|
||||||
|
rss += "<item><title>{0}</title><link>{3}#post{1!s}</link><description>{2}</description></item>".format(post[0],i,post[1][3:-5].replace("<","<").replace(">",">"),config.url)
|
||||||
|
i -= 1
|
||||||
|
blog_posts.reverse()
|
||||||
|
# remove footer
|
||||||
|
# contents += "<hr><p>Generated by <a href='https://github.com/MineRobber9000/blog-gen'>MineRobber's Python blog generator.</a><br><a href='{}'>RSS Feed</a></p>".format(config.rss_url)
|
||||||
|
contents += "</body></html>"
|
||||||
|
rss += "</channel></rss>"
|
||||||
|
with open(config.location,"wb") as f:
|
||||||
|
f.write(contents);
|
||||||
|
with open(config.rss_location,"wb") as f2:
|
||||||
|
f2.write(rss)
|
||||||
|
if tweetlink():
|
||||||
|
with open(config.tweetlink_location,"wb") as f3:
|
||||||
|
f3.write("<script>document.location='https://twitter.com/home?status={}'+document.location.hash.split('').slice(1).join('')</script>".format(config.tweetlink_format).format(config.title,config.url))
|
||||||
|
elif choice.lower() == "q":
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
print("Unknown option: "+choice)
|
||||||
|
cPickle.dump(blog_posts,open("posts.pickled","wb"))
|
||||||
|
return True
|
||||||
|
if not args.workflow and not args.generate:
|
||||||
|
while running:
|
||||||
|
running = loop(raw_input("> "))
|
||||||
|
print("bye")
|
||||||
|
elif not args.workflow and args.generate:
|
||||||
|
loop("g")
|
||||||
|
loop("q")
|
||||||
|
else:
|
||||||
|
loop("n")
|
||||||
|
loop("g")
|
||||||
|
loop("q")
|
|
@ -0,0 +1,12 @@
|
||||||
|
title = "Minecraft News"
|
||||||
|
url = "http://mcupdate.tumblr.com"
|
||||||
|
location = "/home/minerobber/mcuserv/mcus/index.html"
|
||||||
|
rss_location = "/home/minerobber/mcuserv/mcus/feed.xml"
|
||||||
|
rss_url = "http://mcupdate.tumblr.com/feed.xml"
|
||||||
|
desc = "Minecraft News replacement with static files"
|
||||||
|
css_location = "http://mcupdate.tumblr.com/index.css"
|
||||||
|
tweetlink_do = False
|
||||||
|
tweetlink_location = ""
|
||||||
|
tweetlink_url = ""
|
||||||
|
tweetlink_format = "I just read an article from \"{}\":"
|
||||||
|
tweetlink_format += " {}%23" # don't change this!
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,16 @@
|
||||||
|
(lp1
|
||||||
|
(lp2
|
||||||
|
S'Test'
|
||||||
|
p3
|
||||||
|
accopy_reg
|
||||||
|
_reconstructor
|
||||||
|
p4
|
||||||
|
(cmarkdown2
|
||||||
|
UnicodeWithAttrs
|
||||||
|
p5
|
||||||
|
c__builtin__
|
||||||
|
unicode
|
||||||
|
p6
|
||||||
|
V<p>This is a test, stupid.</p>\u000a
|
||||||
|
tRp7
|
||||||
|
aa.
|
|
@ -0,0 +1,68 @@
|
||||||
|
import pyserv, sys
|
||||||
|
|
||||||
|
# Adding response headers to this list will put them in any response that doesn't need specific headers.
|
||||||
|
DEFAULT_RESPONSE_HEADERS = dict(Server=pyserv.VERSION)
|
||||||
|
|
||||||
|
ending_mimes = {}
|
||||||
|
ending_mimes["html"] = "text/html"
|
||||||
|
ending_mimes["css"] = "text/css"
|
||||||
|
ending_mimes["png"] = "image/png"
|
||||||
|
|
||||||
|
class TestServ(pyserv.HTTPServer):
|
||||||
|
def __init__(self):
|
||||||
|
# Initialize base class.
|
||||||
|
super().__init__()
|
||||||
|
# Register GET and POST methods.
|
||||||
|
self.register("GET",self.do_GET)
|
||||||
|
self.register("POST",self.do_POST)
|
||||||
|
# Add registries for other reasons
|
||||||
|
self.contents = dict()
|
||||||
|
self.mime = dict()
|
||||||
|
self.cgi = dict()
|
||||||
|
self.post = dict()
|
||||||
|
# Add index.html content to be statically served.
|
||||||
|
# with open("mcus.html") as f:
|
||||||
|
# self.registerPage("/",[l.rstrip() for l in f],"text/html")
|
||||||
|
# self.registerCGI("/",lambda x,y: [m.replace("PATH",x) for m in self.contents["/"]])
|
||||||
|
|
||||||
|
def registerPage(self,path,content,mime="text/plain"):
|
||||||
|
self.contents[path] = content
|
||||||
|
self.mime[path] = mime
|
||||||
|
|
||||||
|
def registerCGI(self,path,func):
|
||||||
|
self.cgi[path]=func
|
||||||
|
|
||||||
|
def registerPost(self,path,func):
|
||||||
|
self.post[path] = func
|
||||||
|
|
||||||
|
# def greetingform(self,path,headers,data):
|
||||||
|
# with open("greetings.html") as f:
|
||||||
|
# return 200, DEFAULT_RESPONSE_HEADERS, [l.rstrip().format(pyserv.lazySan(data['name'][0] if data.get('name') else "stranger")) for l in f]
|
||||||
|
|
||||||
|
def do_GET(self,method,path,headers):
|
||||||
|
resp_headers = dict(Server=pyserv.VERSION)
|
||||||
|
# with open("mcus.html") as f:
|
||||||
|
# contents = [l.rstrip() for l in f]
|
||||||
|
print("GET {}".format(path),file=sys.stderr)
|
||||||
|
path = path if path!="/" else "index.html"
|
||||||
|
try:
|
||||||
|
with open("mcus/{}".format(path)) as f:
|
||||||
|
contents = [f.read()]
|
||||||
|
if path.split(".")[-1] in ending_mimes:
|
||||||
|
resp_headers["Content-Type"] = ending_mimes[path.split(".")[-1]]
|
||||||
|
except FileNotFoundError:
|
||||||
|
return pyserv.abort(404)
|
||||||
|
return self.formatResponse(200,resp_headers,contents) # Send contents along.
|
||||||
|
|
||||||
|
def do_POST(self,m,path,headers,data):
|
||||||
|
if path in self.post:
|
||||||
|
try:
|
||||||
|
# CGI functions return response code, response headers, and contents.
|
||||||
|
respcode, resp_headers, contents = self.post[path](path,headers,pyserv.unquote(data))
|
||||||
|
# This data is directly passed into formatResponse to get a formatted response.
|
||||||
|
return self.formatResponse(respcode,resp_headers,contents)
|
||||||
|
except pyserv.Abort404: # Abort404 error is a signal
|
||||||
|
return pyserv.abort(404)
|
||||||
|
except: # If something else goes wrong, it's an internal error.
|
||||||
|
return pyserv.abort(500)
|
||||||
|
return pyserv.abort(405) # If path isn't in self.post, we aren't going to bother asking GET for it.
|
Binary file not shown.
After Width: | Height: | Size: 434 B |
|
@ -0,0 +1 @@
|
||||||
|
<?xml version='1.0' encoding='UTF-8'?><rss version='2.0'><channel><title>Minecraft News</title><link>http://mcupdate.tumblr.com</link><description>Minecraft News replacement with static files</description><item><title>Test</title><link>http://mcupdate.tumblr.com#post1</link><description>This is a test, stupid.</description></item></channel></rss>
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,35 @@
|
||||||
|
body {
|
||||||
|
font-family: sans-serif;
|
||||||
|
background-color: #222222;
|
||||||
|
background-image: url('https://static.tumblr.com/qfn3sex/ezom7y7iq/bg_main.png');
|
||||||
|
color: #e0d0d0;
|
||||||
|
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
color: #aaaaff;
|
||||||
|
}
|
||||||
|
hr {
|
||||||
|
border: 0;
|
||||||
|
color: #111111;
|
||||||
|
background-color: #111111;
|
||||||
|
height: 2px;
|
||||||
|
}
|
||||||
|
h3 {
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
img {
|
||||||
|
border:0;
|
||||||
|
margin:0;
|
||||||
|
}
|
||||||
|
.sidebar {
|
||||||
|
padding:10px;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
.alert {
|
||||||
|
background-color:#aa0000;
|
||||||
|
color:#ffffff;
|
||||||
|
font-weight:bold;
|
||||||
|
padding: 6px 10px;
|
||||||
|
width:500px;
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
<html><head><title>Minecraft News</title><link rel='stylesheet' href='http://mcupdate.tumblr.com/index.css'></head><body><h1>Minecraft News</h1><h2 id='post1'>Test</h2><p>This is a test, stupid.</p>
|
||||||
|
</body></html>
|
|
@ -0,0 +1,66 @@
|
||||||
|
from urllib.parse import parse_qs as upunquote
|
||||||
|
VERSION = "PyServ Beta 0.1"
|
||||||
|
|
||||||
|
class Abort404(Exception):
|
||||||
|
"""Signals for a 404 error, if listened for"""
|
||||||
|
def __init__(self,*args):
|
||||||
|
super().__init__(*args)
|
||||||
|
|
||||||
|
# Unquotes query strings.
|
||||||
|
def unquote(*args):
|
||||||
|
return upunquote(*args)
|
||||||
|
|
||||||
|
# Lazy text sanitizer.
|
||||||
|
def lazySan(s):
|
||||||
|
return s.replace("<","<").replace("&","&")
|
||||||
|
|
||||||
|
class HTTPServer:
|
||||||
|
"""HTTP Server base class"""
|
||||||
|
def __init__(self):
|
||||||
|
self.methods = dict()
|
||||||
|
|
||||||
|
"""Registers function to handle requests to a method"""
|
||||||
|
def register(self,method,f):
|
||||||
|
self.methods[method]=f
|
||||||
|
|
||||||
|
"""Returns whether a function is set to handle the given method"""
|
||||||
|
def hasMethod(self,method):
|
||||||
|
return method in self.methods.keys()
|
||||||
|
|
||||||
|
"""Returns a list of supported methods."""
|
||||||
|
def getMethods(self):
|
||||||
|
ret = list(self.methods.keys())
|
||||||
|
ret.sort()
|
||||||
|
return ret
|
||||||
|
|
||||||
|
"""Formats a response."""
|
||||||
|
def formatResponse(self,respcode,headers,content):
|
||||||
|
lines = []
|
||||||
|
lines.append("HTTP/1.1 {!s} {}".format(respcode,DEFAULT_MSGS[respcode]))
|
||||||
|
for h in headers:
|
||||||
|
lines.append("{}: {}".format(h,headers[h]))
|
||||||
|
lines.append("") # of course
|
||||||
|
for line in content:
|
||||||
|
lines.append(line)
|
||||||
|
return "\n".join(lines)+"\n"
|
||||||
|
|
||||||
|
# This function creates a stock error page.
|
||||||
|
# TODO: Replace DEFAULT_MSGS with maintained database
|
||||||
|
DEFAULT_MSGS = {100: "Continue",101: "Switching Protocols",102: "Processing",200: "Ok",201: "Created",202: "Accepted",203: "Non Authoritative Information",204: "No Content",205: "Reset Content",206: "Partial Content",207: "Multi Status",208: "Already Reported",226: "Im Used",300: "Multiple Choices",301: "Moved Permanently",302: "Found",303: "See Other",304: "Not Modified",305: "Use Proxy",307: "Temporary Redirect",308: "Permanent Redirect",400: "Bad Request",401: "Unauthorized",402: "Payment Required",403: "Forbidden",404: "Not Found",405: "Method Not Allowed",406: "Not Acceptable",407: "Proxy Authentication Required",408: "Request Timeout",409: "Conflict",410: "Gone",411: "Length Required",412: "Precondition Failed",413: "Request Entity Too Large",414: "Request Uri Too Long",415: "Unsupported Media Type",416: "Requested Range Not Satisfiable",417: "Expectation Failed",418: "I'm A Teapot",422: "Unprocessable Entity",423: "Locked",424: "Failed Dependency",426: "Upgrade Required",428: "Precondition Required",429: "Too Many Requests",431: "Request Header Fields Too Large",500: "Internal Server Error",501: "Not Implemented",502: "Bad Gateway",503: "Service Unavailable",504: "Gateway Timeout",505: "Http Version Not Supported",506: "Variant Also Negotiates",507: "Insufficient Storage",508: "Loop Detected",510: "Not Extended",511: "Network Authentication Required"}
|
||||||
|
def abort(errorcode,message=None,headers=dict()):
|
||||||
|
lines = []
|
||||||
|
if message is None:
|
||||||
|
message = DEFAULT_MSGS.get(errorcode,"Unknown response code")
|
||||||
|
lines.append("HTTP/1.1 {!s} {}".format(errorcode,message))
|
||||||
|
headers["Server"] = headers.get("Server",VERSION)
|
||||||
|
if errorcode!=204 and "Content-Type" not in headers:
|
||||||
|
headers["Content-Type"] = "text/html"
|
||||||
|
for h in headers:
|
||||||
|
lines.append("{}: {}".format(h,headers[h]))
|
||||||
|
if errorcode==204:
|
||||||
|
return "\n".join(lines)+"\n" # No content. Duh.
|
||||||
|
lines.append("")
|
||||||
|
lines.append("<h1>{!s} {}</h1>".format(errorcode, message))
|
||||||
|
lines.append("<hr>")
|
||||||
|
lines.append("<p>{}</p>".format(headers["Server"]))
|
||||||
|
return "\n".join(lines)+"\n"
|
|
@ -0,0 +1,42 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
import sys, pyserv, mcus
|
||||||
|
|
||||||
|
# Change this to change which server class gets used. Should be a subclass of pyserv.HTTPServer.
|
||||||
|
server = mcus.TestServ()
|
||||||
|
|
||||||
|
# Example registration (in server __init__ method)
|
||||||
|
# self.register("GET",lambda m,p,h: pyserv.abort(418))
|
||||||
|
# The above will make any GET request return the I'm a Teapot code.
|
||||||
|
|
||||||
|
# Distance from end of string to literal "HTTP". Should be 8 in all requests.
|
||||||
|
def httpDistance(l):
|
||||||
|
return (l[::-1].find("PTTH"))+len("PTTH")
|
||||||
|
|
||||||
|
# Read all lines of data sent to us by browser/client
|
||||||
|
inlines = []
|
||||||
|
a = sys.stdin.readline().rstrip()
|
||||||
|
while a:
|
||||||
|
inlines.append(a)
|
||||||
|
a = sys.stdin.readline().rstrip()
|
||||||
|
# Validate HTTP request
|
||||||
|
# In all valid HTTP requests, "HTTP/X.X" is 8 characters from the end.
|
||||||
|
if httpDistance(inlines[0])!=8:
|
||||||
|
print(pyserv.abort(400))
|
||||||
|
# Parse headers
|
||||||
|
headers = dict()
|
||||||
|
for l in inlines[1:]:
|
||||||
|
p = l.split(": ",1)
|
||||||
|
if p[0] in headers:
|
||||||
|
continue
|
||||||
|
headers[p[0]] = p[1]
|
||||||
|
# Parse request
|
||||||
|
method, path, version = inlines[0].split(" ")
|
||||||
|
# If the server doesn't have the method, it isn't implemented.
|
||||||
|
if not server.hasMethod(method):
|
||||||
|
print(pyserv.abort(501))
|
||||||
|
if method=="POST":
|
||||||
|
# Read query-string and call function
|
||||||
|
print(server.methods[method](method, path, headers, sys.stdin.read(int(headers["Content-Length"]))))
|
||||||
|
else:
|
||||||
|
# Just call the function
|
||||||
|
print(server.methods[method](method, path, headers))
|
Loading…
Reference in New Issue