Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a9e9ed6545 | |||
| 4d4a44fd06 |
50
README.md
50
README.md
@@ -1,10 +1,56 @@
|
||||
# PyWebServer
|
||||
|
||||
## Current state
|
||||
Currently I'm in the middle of bringing major improvements to PyWebServer, like a significantly better config
|
||||
automatic HTTPS certificate generation using Let's Encrypt, HTTP `PUT`, `POST` and `DELETE` support,
|
||||
HTTP2 support and auto-indexing.
|
||||
This all will be part of major release 2, version 2.0. These improvements will partially make their way into 1.x
|
||||
versions, from 1.4 onwards, mostly as testing ground and compatibility with 2.0 reasons. Most will probably be dead code
|
||||
until 2.0 properly comes out. Until then, parts of the code may be pretty cluttered. I'll probably enable you to test some feautres
|
||||
like the new config, but code quality is currently not a main priority.
|
||||
|
||||
## GitHub
|
||||
The upstream of this project is on my own [Gitea instance](https://git.novacow.ch/Nova/PyWebServer/).
|
||||
The host of this project is on my own [Gitea instance](https://git.novacow.ch/Nova/PyWebServer/).
|
||||
Because of that I'll mostly reply to issues and PRs there, you can submit issues and PRs on GitHub, but it might take longer before I read it.
|
||||
|
||||
## Installing
|
||||
### The little tiny easier route
|
||||
Installing and running PyWebServer is very simple.
|
||||
First off, download the latest release from the 'Releases' tab, choose the Zip variant if unsure.
|
||||
When it's done downloading, unpack the files in a directory of choice, for the purpose of this README,
|
||||
I've chosen `./pywebserver/` (for Windows: `.\pywebserver\`).
|
||||
From there, open up your favorite text editor and open the file `pywebsrv.conf` in the directory you unpacked PyWebServer.
|
||||
In there, you should see this somewhere:
|
||||
```
|
||||
# Here you choose what directory PyWebServer looks in for files.
|
||||
directory:<Enter directory here>
|
||||
```
|
||||
After the colon, enter your directory where you have your website stored.
|
||||
After that, make sure you have installed Python. Here's how you can install Python:
|
||||
Linux:
|
||||
```bash
|
||||
sudo apt install python3 # Debian / Ubuntu
|
||||
sudo dnf install python3 # Fedora / Nobara
|
||||
sudo pacman -S python3 # Arch and derivatives.
|
||||
```
|
||||
macOS:
|
||||
```bash
|
||||
brew install python3
|
||||
```
|
||||
Windows:
|
||||
```powershell
|
||||
# You can change the `3.12` with whatever version you want/need.
|
||||
winget install -e --id Python.Python.3.12 --scope machine
|
||||
```
|
||||
Then, in the terminal window you have open, go to the directory you unpacked PyWebServer and type this:
|
||||
```
|
||||
python3 ./pywebsrv.py
|
||||
# For Windows users, if the above command doesn't work, try this:
|
||||
py ./pywebsrv.py
|
||||
```
|
||||
And there you go! You've now set up PyWebServer!
|
||||
|
||||
### The little tiny harder route
|
||||
Installing and running PyWebServer is very simple.
|
||||
Assuming you're running Linux:
|
||||
```bash
|
||||
@@ -48,4 +94,4 @@ For every 1.x version there will be support until 2 newer versions come out.
|
||||
So that means that 1.0 will still be supported when 1.1 comes out, but no longer be supported when 1.2 comes out.
|
||||
### 2.x
|
||||
I am planning on releasing a 2.x version with will have a lot more advanced features, like nginx's server block emulation amongst other things.
|
||||
When 2.0 will come out, the last version of 1.x will be supported for a while longer, but no new features will be added.
|
||||
When 2.0 will come out, 1.x will be developed further, but 2.0 will be the main focus.
|
||||
|
||||
92
pywebsrv.py
92
pywebsrv.py
@@ -12,6 +12,8 @@ License:
|
||||
Contact:
|
||||
E-mail: nova@novacow.ch
|
||||
|
||||
NOTE: Once 2.0 is released, PyWebServer will become the Amethyst Web Server
|
||||
|
||||
This is PyWebServer, an ultra minimalist webserver, meant to still have
|
||||
a lot standard webserver features. A comprehensive list is below:
|
||||
Features:
|
||||
@@ -40,39 +42,35 @@ import mimetypes
|
||||
import threading
|
||||
import ssl
|
||||
import socket
|
||||
import re
|
||||
import signal
|
||||
import sys
|
||||
|
||||
try:
|
||||
from certgen import AutoCertGen
|
||||
except ImportError:
|
||||
print(
|
||||
"WARN: You need the AutoCertGen plugin! Please install it from\n"
|
||||
"https://git.novacow.ch/Nova/AutoCertGen/"
|
||||
)
|
||||
# just do nothing, it's not working anyway.
|
||||
# print(
|
||||
# "WARN: You need the AutoCertGen plugin! Please install it from\n"
|
||||
# "https://git.novacow.ch/Nova/AutoCertGen/"
|
||||
# )
|
||||
pass
|
||||
|
||||
|
||||
class FileHandler:
|
||||
CONFIG_FILE = "pywebsrv.conf"
|
||||
DEFAULT_CONFIG = (
|
||||
"port:8080\nport-https:8443\nhttp:1"
|
||||
"\nhttps:0\ndirectory:{cwd}\nhost:localhost"
|
||||
"\nallow-localhost:1"
|
||||
)
|
||||
|
||||
def __init__(self, base_dir=None):
|
||||
self.config_path = os.path.join(os.getcwd(), self.CONFIG_FILE)
|
||||
self.base_dir = self.read_config("directory")
|
||||
|
||||
def check_first_run(self):
|
||||
if not os.path.isfile(self.config_path):
|
||||
self.on_first_run()
|
||||
return True
|
||||
return False
|
||||
|
||||
def on_first_run(self):
|
||||
with open(self.config_path, "w") as f:
|
||||
f.write(self.DEFAULT_CONFIG.format(cwd=os.getcwd()))
|
||||
self.cached_conf = None
|
||||
if not os.path.exists(self.config_path):
|
||||
print(
|
||||
"The pywebsrv.conf file needs to be in the same directory "
|
||||
"as pywebsrv.py! Get the default config file from:\n"
|
||||
"https://git.novacow.ch/Nova/PyWebServer/raw/branch/main/pywebsrv.conf"
|
||||
)
|
||||
exit(1)
|
||||
|
||||
def read_file(self, file_path):
|
||||
if "../" in file_path:
|
||||
@@ -163,9 +161,55 @@ class FileHandler:
|
||||
return value
|
||||
return None
|
||||
|
||||
def read_new_config(self, option, host=None):
|
||||
"""
|
||||
Reads the configuration file and returns a dict
|
||||
"""
|
||||
if self.cached_conf is None:
|
||||
with open(self.config_path, "r", encoding="utf-8") as fh:
|
||||
text = fh.read()
|
||||
|
||||
blocks = re.findall(
|
||||
r'^(host\s+(\S+)|globals)\s*\{([^}]*)\}', text, re.MULTILINE
|
||||
)
|
||||
parsed = {}
|
||||
host_list = []
|
||||
for tag, hostname, body in blocks:
|
||||
section = hostname if hostname else "globals"
|
||||
if hostname:
|
||||
host_list.append(hostname)
|
||||
kv = {}
|
||||
for line in body.splitlines():
|
||||
line = line.strip()
|
||||
if not line or ":" not in line or line.starswith("#"):
|
||||
continue
|
||||
|
||||
key, rest = line.split(":", 1)
|
||||
key = key.strip()
|
||||
rest = rest.strip()
|
||||
|
||||
# Split comma-separated values (e.g. GET,PUT)
|
||||
if "," in rest:
|
||||
kv[key] = [item.strip() for item in rest.split(",")]
|
||||
else:
|
||||
kv[key] = rest
|
||||
parsed[section] = kv
|
||||
parsed["globals"]["hosts"] = host_list
|
||||
self.cached_conf = parsed
|
||||
else:
|
||||
parsed = self.cached_conf
|
||||
if option == "host":
|
||||
try:
|
||||
return host_list
|
||||
except Exception:
|
||||
return parsed["globals"]["hosts"]
|
||||
section = parsed.get(host or "globals", {})
|
||||
return section.get(option)
|
||||
|
||||
def autocert(self):
|
||||
"""
|
||||
Generate some self-signed certificates using AutoCertGen
|
||||
TODO: doesn't work, need to fix. probably add `./` to $PATH
|
||||
"""
|
||||
autocert = AutoCertGen()
|
||||
autocert.gen_cert()
|
||||
@@ -229,6 +273,12 @@ class RequestParser:
|
||||
else:
|
||||
return True
|
||||
|
||||
#
|
||||
# class ProxyServer:
|
||||
# def __init__(
|
||||
# self,
|
||||
# ):
|
||||
|
||||
|
||||
class WebServer:
|
||||
def __init__(
|
||||
@@ -455,7 +505,7 @@ class WebServer:
|
||||
status_message = messages.get(status_code)
|
||||
headers = (
|
||||
f"HTTP/1.1 {status_code} {status_message}\r\n"
|
||||
f"Server: PyWebServer/1.2.1\r\n"
|
||||
f"Server: PyWebServer/1.4\r\n"
|
||||
f"Content-Type: {content_type}\r\n"
|
||||
f"Content-Length: {len(binary_data)}\r\n"
|
||||
f"Connection: close\r\n\r\n"
|
||||
@@ -492,7 +542,7 @@ class WebServer:
|
||||
# Don't encode yet, if 302 status code we have to include location.
|
||||
headers = (
|
||||
f"HTTP/1.1 {status_code} {status_message}\r\n"
|
||||
f"Server: PyWebServer/1.2.1\r\n"
|
||||
f"Server: PyWebServer/1.4\r\n"
|
||||
f"Content-Length: {len(body)}\r\n"
|
||||
f"Connection: close\r\n\r\n"
|
||||
).encode()
|
||||
|
||||
Reference in New Issue
Block a user