or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

build.mdcli.mdfiltering.mdindex.mdmiddleware.mdserver.mdutils.md

middleware.mddocs/

0

# HTTP Middleware

1

2

ASGI middleware for injecting WebSocket client code into HTML responses to enable hot reloading functionality. The middleware automatically adds JavaScript code to HTML pages that establishes a WebSocket connection for real-time browser refresh notifications.

3

4

## Capabilities

5

6

### JavascriptInjectorMiddleware Class

7

8

ASGI middleware that injects WebSocket client JavaScript into HTML responses.

9

10

```python { .api }

11

class JavascriptInjectorMiddleware:

12

def __init__(self, app: ASGIApp, ws_url: str) -> None:

13

"""

14

Initialize middleware with ASGI app and WebSocket URL.

15

16

Parameters:

17

- app: ASGIApp - The ASGI application to wrap

18

- ws_url: str - WebSocket URL in format "host:port" for client connections

19

20

Processing:

21

- Pre-generates WebSocket client script using web_socket_script()

22

- Encodes script to UTF-8 bytes for efficient injection

23

- Stores references for use in request handling

24

"""

25

26

async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:

27

"""

28

ASGI middleware callable for HTTP requests.

29

30

Automatically injects WebSocket JavaScript into HTML responses:

31

- Only processes HTTP requests (ignores WebSocket, etc.)

32

- Detects HTML responses by Content-Type header

33

- Adjusts Content-Length header when script is injected

34

- Appends script to response body on final chunk

35

36

Parameters:

37

- scope: Scope - ASGI request scope with connection info

38

- receive: Receive - ASGI receive callable for request messages

39

- send: Send - ASGI send callable for response messages

40

41

Returns:

42

- None

43

44

Behavior:

45

- Non-HTTP requests: Pass through unchanged

46

- Non-HTML responses: Pass through unchanged

47

- HTML responses: Inject WebSocket script at end of body

48

- Streaming responses: Only inject on final body chunk

49

"""

50

```

51

52

### WebSocket Script Generation

53

54

Generate the JavaScript code that browsers execute to establish WebSocket connections.

55

56

```python { .api }

57

def web_socket_script(ws_url: str) -> str:

58

"""

59

Generate WebSocket JavaScript code for browser auto-reload.

60

61

Creates a complete HTML script block with WebSocket client code that:

62

- Establishes WebSocket connection to specified URL

63

- Listens for messages from server

64

- Triggers browser reload on any message received

65

66

Parameters:

67

- ws_url: str - WebSocket URL in format "host:port"

68

69

Returns:

70

- str - Complete HTML script element with WebSocket client code

71

72

Generated Script Behavior:

73

- Creates WebSocket connection to "ws://{ws_url}/websocket-reload"

74

- Automatically reloads page when server sends any message

75

- Handles connection errors gracefully (no error handling shown to user)

76

"""

77

```

78

79

## Usage Examples

80

81

### Basic Middleware Setup

82

83

```python

84

from starlette.applications import Starlette

85

from starlette.middleware import Middleware

86

from starlette.staticfiles import StaticFiles

87

from sphinx_autobuild.middleware import JavascriptInjectorMiddleware

88

89

# Create ASGI application with middleware

90

app = Starlette(

91

middleware=[

92

Middleware(JavascriptInjectorMiddleware, ws_url="127.0.0.1:8000")

93

]

94

)

95

```

96

97

### Complete Server Integration

98

99

```python

100

from starlette.applications import Starlette

101

from starlette.middleware import Middleware

102

from starlette.routing import Mount, WebSocketRoute

103

from starlette.staticfiles import StaticFiles

104

from sphinx_autobuild.middleware import JavascriptInjectorMiddleware

105

from sphinx_autobuild.server import RebuildServer

106

107

# Setup components

108

server = RebuildServer(watch_dirs, ignore_filter, builder)

109

url_host = "127.0.0.1:8000"

110

111

# Create app with middleware and routes

112

app = Starlette(

113

routes=[

114

WebSocketRoute("/websocket-reload", server, name="reload"),

115

Mount("/", app=StaticFiles(directory="_build/html", html=True), name="static"),

116

],

117

middleware=[

118

Middleware(JavascriptInjectorMiddleware, ws_url=url_host)

119

],

120

lifespan=server.lifespan,

121

)

122

```

123

124

### Custom Script Generation

125

126

```python

127

from sphinx_autobuild.middleware import web_socket_script

128

129

# Generate script for different URLs

130

local_script = web_socket_script("127.0.0.1:8000")

131

network_script = web_socket_script("192.168.1.100:8080")

132

133

print(local_script)

134

# Output:

135

# <script>

136

# const ws = new WebSocket("ws://127.0.0.1:8000/websocket-reload");

137

# ws.onmessage = () => window.location.reload();

138

# </script>

139

140

# Use in custom middleware or manual injection

141

custom_html = f"""

142

<!DOCTYPE html>

143

<html>

144

<head><title>My Docs</title></head>

145

<body>

146

<h1>Documentation</h1>

147

{network_script}

148

</body>

149

</html>

150

"""

151

```

152

153

## How Injection Works

154

155

### HTTP Response Processing

156

157

The middleware intercepts HTTP responses and modifies them:

158

159

1. **Request Filtering**: Only processes HTTP requests (not WebSocket)

160

2. **Content-Type Detection**: Checks for "text/html" Content-Type header

161

3. **Header Adjustment**: Updates Content-Length to account for injected script

162

4. **Body Modification**: Appends WebSocket script to final response body chunk

163

5. **Pass-through**: Non-HTML responses are unmodified

164

165

### Script Injection Details

166

167

```python

168

# The generated script is minimal and efficient:

169

script_template = """

170

<script>

171

const ws = new WebSocket("ws://{ws_url}/websocket-reload");

172

ws.onmessage = () => window.location.reload();

173

</script>

174

"""

175

176

# Key characteristics:

177

# - No error handling (fails silently if WebSocket unavailable)

178

# - Automatic reconnection on page reload

179

# - Triggers on any message (server sends "refresh")

180

# - Minimal performance impact

181

```

182

183

### Content-Length Handling

184

185

The middleware properly handles HTTP Content-Length headers:

186

187

```python

188

# When HTML is detected:

189

if "Content-Length" in headers:

190

original_length = int(headers["Content-Length"])

191

new_length = original_length + len(script_bytes)

192

headers["Content-Length"] = str(new_length)

193

194

# This ensures:

195

# - HTTP/1.1 clients receive correct content length

196

# - Proxy servers handle responses correctly

197

# - No truncation of response body

198

```

199

200

## Browser Compatibility

201

202

### WebSocket Support

203

204

The injected JavaScript requires WebSocket support:

205

- **Modern Browsers**: Full support (Chrome, Firefox, Safari, Edge)

206

- **Legacy Browsers**: IE 10+, older mobile browsers may need polyfills

207

- **Fallback**: Pages still function without WebSocket (no auto-reload)

208

209

### Connection Behavior

210

211

```javascript

212

// Browser-side connection handling:

213

const ws = new WebSocket("ws://127.0.0.1:8000/websocket-reload");

214

215

// Automatic behaviors:

216

ws.onopen = () => {

217

// Connection established - ready for reload messages

218

};

219

220

ws.onmessage = () => {

221

window.location.reload(); // Refresh entire page

222

};

223

224

ws.onclose = () => {

225

// Connection lost - will reconnect on next page load

226

};

227

228

ws.onerror = () => {

229

// Connection error - fails silently, no user notification

230

};

231

```

232

233

## Security Considerations

234

235

### Cross-Origin Policy

236

237

WebSocket connections respect browser security policies:

238

- **Same-origin**: Works automatically for same-origin requests

239

- **Cross-origin**: May require CORS configuration for different hosts

240

- **Mixed content**: HTTPS pages cannot connect to WS (non-secure WebSocket)

241

242

### Development vs Production

243

244

This middleware is designed for **development only**:

245

- **Performance**: Adds overhead to every HTML response

246

- **Security**: Injects arbitrary JavaScript into responses

247

- **Functionality**: Hot reloading is not needed in production

248

249

**Production Deployment**: Remove middleware from production deployments:

250

251

```python

252

# Development configuration

253

if DEBUG:

254

middleware.append(Middleware(JavascriptInjectorMiddleware, ws_url=url_host))

255

256

# Or use environment-based configuration

257

import os

258

if os.getenv("SPHINX_AUTOBUILD_DEV"):

259

middleware.append(Middleware(JavascriptInjectorMiddleware, ws_url=url_host))

260

```

261

262

## Performance Characteristics

263

264

### Injection Overhead

265

266

- **CPU**: Minimal - simple string concatenation on final body chunk

267

- **Memory**: Low - pre-generated script cached in middleware instance

268

- **Network**: Small - typically 100-200 bytes added to each HTML response

269

- **Latency**: Negligible - injection happens during response streaming

270

271

### Selective Processing

272

273

Middleware optimizes by only processing relevant requests:

274

- **HTTP Only**: Ignores WebSocket, SSE, and other protocol requests

275

- **HTML Only**: Skips CSS, JavaScript, images, and other content types

276

- **Final Chunk**: Only modifies the last body chunk in streaming responses

277

- **Header Check**: Early Content-Type detection avoids unnecessary processing