Skip to content

Latest commit

 

History

History
470 lines (356 loc) · 9.46 KB

HTTP2_GUIDE.md

File metadata and controls

470 lines (356 loc) · 9.46 KB

HTTP/2 Support in MoroJS

MoroJS now includes full native HTTP/2 support with advanced features including server push, stream prioritization, and multiplexing.

Features

  • ✅ Native HTTP/2 Server - Built on Node.js http2 module
  • ✅ Server Push - Proactively push resources to clients
  • ✅ Stream Prioritization - Control resource loading order
  • ✅ Multiplexing - Multiple requests over single connection
  • ✅ Header Compression - HPACK compression built-in
  • ✅ HTTP/1.1 Fallback - Automatic fallback support
  • ✅ Auto-detect Push - Automatically detect and push CSS/JS from HTML

Quick Start

Basic HTTP/2 Server

import { Moro } from '@morojs/moro';
import * as fs from 'fs';

const app = new Moro({
  http2: true,
  https: {
    key: fs.readFileSync('key.pem'),
    cert: fs.readFileSync('cert.pem'),
  },
});

app.get('/', (req, res) => {
  res.json({
    message: 'Hello HTTP/2!',
    version: req.httpVersion, // '2.0'
  });
});

app.listen(3000);

Advanced Configuration

const app = new Moro({
  http2: {
    allowHTTP1: true, // Support HTTP/1.1 fallback
    settings: {
      maxConcurrentStreams: 100,
      initialWindowSize: 65535,
      maxFrameSize: 16384,
    },
  },
  https: {
    key: fs.readFileSync('key.pem'),
    cert: fs.readFileSync('cert.pem'),
  },
});

Server Push

Automatic Push (Recommended)

Use the http2.push middleware with auto-detection:

import { middleware } from '@morojs/moro';

app.use(
  middleware.http2.push({
    autoDetect: true, // Auto-detect CSS/JS from HTML
    resources: [
      {
        path: '/styles/main.css',
        as: 'style',
        type: 'text/css',
        priority: 200, // High priority
      },
      {
        path: '/scripts/app.js',
        as: 'script',
        type: 'application/javascript',
        priority: 150,
      },
    ],
    condition: req => req.path === '/' || req.path.endsWith('.html'),
  })
);

Manual Push

Push resources manually within route handlers:

app.get('/', (req, res) => {
  // Check if push is available
  if (res.push) {
    // Push CSS with high priority
    const cssStream = res.push('/styles/main.css', {
      headers: { 'content-type': 'text/css' },
      priority: 200,
    });

    if (cssStream) {
      cssStream.end('body { font-family: Arial; }');
    }

    // Push JavaScript
    res.push('/scripts/app.js', {
      headers: { 'content-type': 'application/javascript' },
      priority: 150,
    });
  }

  res.send('<html>...</html>');
});

Stream Prioritization

Control the priority of your responses:

// Critical API endpoint - highest priority
app.get('/api/critical', (req, res) => {
  if (res.setPriority) {
    res.setPriority({
      weight: 256, // 1-256, higher = more important
      exclusive: true, // Exclusively process this stream
    });
  }

  res.json({ critical: 'data' });
});

// Background data - low priority
app.get('/api/background', (req, res) => {
  if (res.setPriority) {
    res.setPriority({ weight: 1 });
  }

  res.json({ background: 'data' });
});

Priority Values

Priority weight ranges from 1-256:

  • 256 - Critical resources (HTML, critical API data)
  • 200 - High priority (CSS, fonts)
  • 150 - Medium priority (JavaScript)
  • 100 - Normal priority (images)
  • 50 - Low priority (analytics, tracking)
  • 1 - Lowest priority (background tasks)

Checking HTTP Version

app.get('/', (req, res) => {
  const httpVersion = req.httpVersion; // '2.0' or '1.1'

  if (httpVersion === '2.0') {
    // Use HTTP/2 features
    res.push('/assets/critical.css');
  }

  res.json({ version: httpVersion });
});

SSL Certificates

HTTP/2 requires SSL/TLS. For development, generate self-signed certificates:

# Generate self-signed certificate
openssl req -x509 -newkey rsa:2048 -nodes -sha256 \
  -subj '/CN=localhost' \
  -keyout localhost-key.pem \
  -out localhost-cert.pem

For production, use certificates from a trusted CA like Let's Encrypt.

Best Practices

1. Use Server Push Wisely

Only push resources that are:

  • Critical for initial render
  • Small in size (< 100KB)
  • Used by most users
app.use(
  middleware.http2.push({
    autoDetect: true, // Let MoroJS detect from HTML
    resources: [
      // Only push critical resources
      { path: '/critical.css', as: 'style', type: 'text/css', priority: 200 },
    ],
  })
);

2. Set Appropriate Priorities

// Critical path
res.setPriority({ weight: 256, exclusive: true });

// Important but not critical
res.setPriority({ weight: 200 });

// Background/analytics
res.setPriority({ weight: 1 });

3. Enable HTTP/1.1 Fallback

const app = new Moro({
  http2: {
    allowHTTP1: true, // Clients without HTTP/2 support
  },
  https: {/* ... */},
});

4. Monitor Server Push

app.get('/', (req, res) => {
  const pushStream = res.push('/asset.css');

  if (pushStream) {
    pushStream.on('error', err => {
      console.log('Push failed:', err.message);
    });
  }
});

Performance Tuning

Adjust Concurrent Streams

const app = new Moro({
  http2: {
    settings: {
      maxConcurrentStreams: 100, // Default: 100
      initialWindowSize: 65535, // 64KB
      maxFrameSize: 16384, // 16KB
    },
  },
});

Connection Settings

const app = new Moro({
  http2: {
    maxSessionMemory: 10, // MB per session
    settings: {
      headerTableSize: 4096, // HPACK table size
      maxHeaderListSize: 8192, // Max header size
    },
  },
});

Middleware Compatibility

All MoroJS middleware works with HTTP/2:

import { middleware } from '@morojs/moro';

app.use(middleware.cors());
app.use(middleware.helmet());
app.use(middleware.compression()); // Works alongside HTTP/2 compression
app.use(middleware.requestLogger());

Testing HTTP/2

Using curl

# Test HTTP/2 endpoint
curl -k --http2 https://localhost:3000/

# Check protocol version
curl -k --http2 -I https://localhost:3000/ | grep HTTP

Using Node.js

const http2 = require('http2');

const client = http2.connect('https://localhost:3000', {
  rejectUnauthorized: false, // For self-signed certs
});

const req = client.request({ ':path': '/' });

req.on('response', headers => {
  console.log('Status:', headers[':status']);
});

req.on('data', chunk => {
  console.log('Data:', chunk.toString());
});

req.end();

Browser Testing

Open Chrome DevTools → Network tab → Protocol column shows h2 for HTTP/2.

Migration from HTTP/1.1

Before (HTTP/1.1)

const app = new Moro();

app.get('/', (req, res) => {
  res.send('<html>...</html>');
});

app.listen(3000);

After (HTTP/2)

const app = new Moro({
  http2: true,
  https: {
    key: fs.readFileSync('key.pem'),
    cert: fs.readFileSync('cert.pem'),
  },
});

app.use(middleware.http2.push({ autoDetect: true }));

app.get('/', (req, res) => {
  // Optionally use HTTP/2 features
  if (res.push) {
    res.push('/critical.css');
  }

  res.send('<html>...</html>');
});

app.listen(3000);

Troubleshooting

Server Push Not Working

  1. Check if client supports HTTP/2:

    if (req.httpVersion === '2.0' && res.push) {
      // Push is available
    }
  2. Ensure SSL is configured:

    // HTTP/2 requires HTTPS
    https: {
      key: fs.readFileSync('key.pem'),
      cert: fs.readFileSync('cert.pem'),
    }
  3. Check whether the client allows push. Server push is enabled by the client's SETTINGS_ENABLE_PUSH, not the server's: RFC 9113 forbids a server from advertising it (nghttp2-based clients drop the connection if one does), so settings.enablePush is accepted but never sent. Moro reads the client's choice per stream (stream.pushAllowed) and res.push() returns null when it is off. Chrome and Firefox have disabled push, so expect null from browsers and rely on <link rel="preload"> there.

Connection Issues

  1. Verify certificates are valid
  2. Check maxConcurrentStreams setting
  3. Monitor memory usage with maxSessionMemory

Performance Issues

  1. Don't over-push resources
  2. Set appropriate priorities
  3. Monitor stream counts
  4. Adjust initialWindowSize if needed

Examples

See /examples/http2-server.ts for a complete working example with:

  • Server push configuration
  • Stream prioritization
  • Auto-detection of resources
  • Manual push examples
  • Priority testing endpoints

API Reference

Response Methods (HTTP/2)

res.push(path, options)

Push a resource to the client.

res.push(path: string, options?: {
  headers?: Record<string, string>;
  priority?: number; // 1-256
});

res.setPriority(options)

Set stream priority for current response.

res.setPriority(options?: {
  parent?: number;
  weight?: number; // 1-256
  exclusive?: boolean;
});

Middleware

middleware.http2.push(options)

Configure server push behavior.

middleware.http2.push({
  autoDetect?: boolean;
  resources?: Array<{
    path: string;
    as: string;
    type?: string;
    priority?: number;
  }>;
  condition?: (req) => boolean;
});

Further Reading