Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: CI

on:
push:
branches: [main]
pull_request:

permissions: {}

jobs:
lint:
name: Lint and type-check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
fetch-depth: 0 # hatch-vcs needs tags to build the project for pyright
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
- run: uvx pre-commit run --all-files

test:
name: Test on Python ${{ matrix.python-version }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
fetch-depth: 0 # hatch-vcs needs tags to build the project
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
- run: uv run --python ${{ matrix.python-version }} --group test pytest
24 changes: 24 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: Publish to PyPI

on:
release:
types: [published]

permissions: {}

jobs:
publish:
name: Build and publish
runs-on: ubuntu-latest
environment:
name: pypi
url: https://pypi.org/project/ext_http_server/
permissions:
id-token: write # trusted publishing to PyPI (no API token)
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
fetch-depth: 0 # full history + tags so hatch-vcs can derive the version
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
- run: uv build
- run: uv publish --trusted-publishing always
8 changes: 7 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
*.egg-info/
*.pyc
*~
.coverage
.pytest_cache/
.ruff_cache/
.venv/
__pycache__/
_build/
build/
dist/
*.egg-info/
htmlcov/
55 changes: 55 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
repos:
- hooks:
- id: docstrfmt
require_serial: true
repo: https://github.com/LilSpazJoekp/docstrfmt
rev: 8688ba6420d7b5ca95a8ba0edf8a9953babdc3da # frozen: v2.1.1

- hooks:
- id: codesorter
repo: https://github.com/praw-dev/CodeSorter
rev: 8aa6144b41e0f789124b2ca377d246ffd1fbb317 # frozen: v0.2.7

- hooks:
- id: auto-walrus
repo: https://github.com/MarcoGorelli/auto-walrus
rev: 1743edbed52f3d61886b59d98a27164a8af29c0d # frozen: 0.4.1

- hooks:
- args: [--fix]
id: ruff-check
- id: ruff-format
repo: https://github.com/astral-sh/ruff-pre-commit
rev: 3b3f7c3f57fe9925356faf5fe6230835138be230 # frozen: v0.15.17

- hooks:
- files: ^(.*\.toml)$
id: toml-sort-fix
repo: https://github.com/pappasam/toml-sort
rev: 2970ae9bb7124fe5117a27e10c10d2da051ce05a # frozen: v0.24.4

- hooks:
- id: check-added-large-files
- id: check-executables-have-shebangs
- id: check-shebang-scripts-are-executable
- id: check-toml
- id: check-yaml
- id: end-of-file-fixer
- args: [--fix=lf]
id: mixed-line-ending
- args: [--pytest-test-first]
files: ^tests/.*\.py$
id: name-tests-test
- id: no-commit-to-branch
- id: trailing-whitespace
repo: https://github.com/pre-commit/pre-commit-hooks
rev: 3e8a8703264a2f4a69428a0aa4dcb512790b2c8c # frozen: v6.0.0

- hooks:
- entry: uv run --group dev pyright
id: pyright
language: system
name: pyright
pass_filenames: false
types: [python]
repo: local
1 change: 0 additions & 1 deletion MANIFEST.in

This file was deleted.

26 changes: 23 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,28 @@
# ext_http_server

An extended version of Python's `http.server` that turns a simple static file
server into one supporting HTTPS, HTTP Basic authentication, server-to-client
rate limiting, and resumable downloads via HTTP `Range` requests.

### Requirements

`ext_http_server` supports Python 3.10 through 3.14.

### Installation

pip install ext_http_server
Install the `ext_http_server` command with [uv](https://docs.astral.sh/uv/):

uv tool install ext_http_server

### Generate a certificate

Run the following to generate cert.pem:
Generate a self-signed certificate, writing the private key and certificate
together into `cert.pem` (the single file `--cert` expects). This uses a modern
ECDSA P-256 key, which every common TLS client supports (Ed25519 keys are
newer but are rejected by some clients, including the LibreSSL-based `curl` that
ships with macOS as of June 2026):

openssl req -new -x509 -days 365 -nodes -out cert.pem -keyout cert.pem
openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 -noenc -keyout cert.pem -out cert.pem -days 365 -subj "/CN=localhost"

### Running ext_http_server

Expand All @@ -15,6 +31,10 @@ to serve them up with a max outgoing throughput of 16KBps:

ext_http_server --cert cert.pem -d /tmp/path/to/files -r16 -a foo:bar

Or run it once without installing:

uvx ext_http_server --cert cert.pem -d /tmp/path/to/files -r16 -a foo:bar

By default, you will be able to access the webserver at
[https://localhost:8000](https://localhost:8000). To authenticate, use the
username `foo` and the password `bar` as indicated by the `-a foo:bar`
Expand Down
Loading