# @milaboratories/pframes-serv

A high-performance HTTP server for serving Parquet files with full RFC 9110 compliance, authentication support, and optimized caching.

## Overview

This package provides HTTP(S) server functionality for the PFrames ecosystem, specifically designed for serving Parquet files with proper HTTP semantics. It implements a complete HTTP/1.1 server that handles range requests, conditional requests, Bearer token authentication, and range requests caching.

## Architecture

### Core Components

- **Server** (`src/serve.ts`) - Generic HTTP server with lifecycle management and optional authentication
- **HTTP Handler** (`src/handler.ts`) - RFC 9110 compliant HTTP request handler with object store integration
- **File System Store** (`src/fs-store.ts`) - Object store implementation for local filesystem

### Exports

#### Public API

- `serve()` - Main server function
- `createRequestHandler()` - HTTP request handler factory
- `FileSystemStore` - Local file system object store implementation

The `HttpHelpers` export contains functions intended for re-export through the `@milaboratories/pframes-rs-node` package.

### Binary Script

The `bin/parquet-server.mjs` script can be used for integration testing. It provides a standalone server that can be spawned programmatically and serve Parquet files over HTTP(S) from a local directory.

## HTTP Handler Flow

The request handler implements a complete HTTP/1.1 server following [RFC 9110 (HTTP Semantics)](https://datatracker.ietf.org/doc/html/rfc9110) and [RFC 9111 (HTTP Caching)](https://datatracker.ietf.org/doc/html/rfc9111). The handler consists of two layers:

1. **Authorization Layer** (`authorizeRequestHandler`) - Optional Bearer token authentication
2. **Core Handler** (`handleRequest`) - Main HTTP processing logic

## Request Processing Flow

```text
┌─────────────────┐
│ Request Arrives │
└─────────┬───────┘
          │
          ▼
┌─────────────────────┐
│ Set Default Headers │ ← Content-Length: 0, Date: now
│ (All Responses)     │
└─────────┬───────────┘
          │
          ▼
┌─────────────────────┐    [FAIL] Invalid/Missing Token
│ Check Bearer Token  │────────────┐
│ (Timing-Safe)       │            │
└─────────┬───────────┘            │
          │ [PASS] Valid Token     ▼
          │                ┌──────────────┐
          │                │ Return 401   │ ← WWW-Authenticate: Bearer
          │                │ Unauthorized │
          ▼                └──────────────┘
┌─────────────────────┐
│ Set Cache Control   │ ← Cache-Control: public, max-age=31536000
│ Headers             │
└─────────┬───────────┘
          │
          ▼
┌─────────────────────┐    [FAIL] Not GET/HEAD
│ Check HTTP Method   │────────────┐
└─────────┬───────────┘            │
          │ [PASS] GET or HEAD     │
          ▼                        ▼
┌─────────────────────┐    ┌──────────────┐
│ Parse URL &         │    │ Return 405   │ ← Allow: GET, HEAD
│ Extract Filename    │    │ Not Allowed  │
└─────────┬───────────┘    └──────────────┘
          │
          ▼
┌─────────────────────┐    [FAIL] Invalid Pattern
│ Validate .parquet   │────────────┐
│ Extension           │            │
└─────────┬───────────┘            │
          │ [PASS] Valid .parquet  │
          ▼                        ▼
┌─────────────────────┐    ┌──────────────┐
│ Set Content Headers │    │ Return 410   │
│ (Accept-Ranges,     │    │ Gone         │
│  Content-Type),     │    └──────────────┘
│ Generate ETag &     │ ← ETag from filename
│ Set Cache Headers   │   Last-Modified: UTC(0)
└─────────┬───────────┘
          │
          ▼
┌─────────────────────┐    [FAIL] Precondition Failed
│ Check If-Match &    │────────────┐
│ If-Unmodified-Since │            │
└─────────┬───────────┘            │
          │                        │
          ▼                        ▼
┌─────────────────────┐    ┌─────────────────────┐
│ Check If-None-Match │    │ Return 412          │
│ & If-Modified-Since │    │ Precondition Failed │
└─────────┬───────────┘    └─────────────────────┘
          │ [FAIL] Not Modified
          ├────────────────────────┐
          │                        ▼
          │                ┌──────────────┐
          │                │ Return 304   │
          │                │ Not Modified │
          │                └──────────────┘
          ▼
┌─────────────────────┐    [FAIL] Invalid Range
│ Parse Range Header  │────────────┐
└─────────┬───────────┘            │
          │ [PASS] Valid Range     │
          ▼                        ▼
┌─────────────────────┐    ┌──────────────┐
│ Proxy Request to    │    │ Return 400   │
│ Object Store        │    │ Bad Request  │
└─────────┬───────────┘    └──────────────┘
          │
          ├─────────┬─────────┬─────────┐
          │         │         │         │
          ▼         ▼         ▼         ▼
      ┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐
      │  Ok   │ │  Not  │ │Invalid│ │ Server│
      │Success│ │ Found │ │ Range │ │ Error │
      └───┬───┘ └───┬───┘ └───┬───┘ └───┬───┘
          │         │         │         │
          ▼         ▼         ▼         ▼
      ┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐
      │200/206│ │ 404   │ │ 416   │ │ 500   │
      └───┬───┘ └───────┘ └───────┘ └───────┘
          │
          ▼
┌─────────────────────┐
│ Set Content-Length  │ ← Full size or range size
│ & Content-Range     │   Content-Range for 206 responses
└─────────┬───────────┘
          │
          ▼
┌─────────────────────┐    [HEAD] Headers Only
│ Check Request       │────────────┐
│ Method              │            │
└─────────┬───────────┘            │
          │ [GET] Send Body        │
          ▼                        ▼
┌─────────────────────┐    ┌──────────────┐
│ Stream File Content │    │ Send Headers │
└─────────────────────┘    └──────────────┘
```
