update
This commit is contained in:
parent
d8d1bdcd41
commit
200a735876
@ -652,69 +652,69 @@ const domainRouter = (socket: net.Socket, context: IRouteContext) => {
|
|||||||
|
|
||||||
### Step 10: Update Test Files (1.5 hours)
|
### Step 10: Update Test Files (1.5 hours)
|
||||||
#### 10.1 Update Socket Handler Tests
|
#### 10.1 Update Socket Handler Tests
|
||||||
- [ ] Open `test/test.socket-handler.ts`
|
- [x] Open `test/test.socket-handler.ts`
|
||||||
- [ ] Update all handler functions to accept context parameter
|
- [x] Update all handler functions to accept context parameter
|
||||||
- [ ] Open `test/test.socket-handler.simple.ts`
|
- [x] Open `test/test.socket-handler.simple.ts`
|
||||||
- [ ] Update handler to accept context parameter
|
- [x] Update handler to accept context parameter
|
||||||
- [ ] Open `test/test.socket-handler-race.ts`
|
- [x] Open `test/test.socket-handler-race.ts`
|
||||||
- [ ] Update handler to accept context parameter
|
- [x] Update handler to accept context parameter
|
||||||
|
|
||||||
#### 10.2 Find and Update/Delete Redirect Tests
|
#### 10.2 Find and Update/Delete Redirect Tests
|
||||||
- [ ] Search for files containing `type: 'redirect'` in test directory
|
- [x] Search for files containing `type: 'redirect'` in test directory
|
||||||
- [ ] For each file:
|
- [x] For each file:
|
||||||
- [ ] If it's a redirect-specific test, delete the file
|
- [x] If it's a redirect-specific test, delete the file
|
||||||
- [ ] If it's a mixed test, update redirect actions to use socket handlers
|
- [x] If it's a mixed test, update redirect actions to use socket handlers
|
||||||
- [ ] Files to check:
|
- [x] Files to check:
|
||||||
- [ ] `test/test.route-redirects.ts` - likely delete entire file
|
- [x] `test/test.route-redirects.ts` - deleted entire file
|
||||||
- [ ] `test/test.forwarding.ts` - update any redirect tests
|
- [x] `test/test.forwarding.ts` - update any redirect tests
|
||||||
- [ ] `test/test.forwarding.examples.ts` - update any redirect tests
|
- [x] `test/test.forwarding.examples.ts` - update any redirect tests
|
||||||
- [ ] `test/test.route-config.ts` - update any redirect tests
|
- [x] `test/test.route-config.ts` - update any redirect tests
|
||||||
|
|
||||||
#### 10.3 Find and Update/Delete Block Tests
|
#### 10.3 Find and Update/Delete Block Tests
|
||||||
- [ ] Search for files containing `type: 'block'` in test directory
|
- [x] Search for files containing `type: 'block'` in test directory
|
||||||
- [ ] Update or delete as appropriate
|
- [x] Update or delete as appropriate
|
||||||
|
|
||||||
#### 10.4 Find and Delete Static Tests
|
#### 10.4 Find and Delete Static Tests
|
||||||
- [ ] Search for files containing `type: 'static'` in test directory
|
- [x] Search for files containing `type: 'static'` in test directory
|
||||||
- [ ] Delete static-specific test files
|
- [x] Delete static-specific test files
|
||||||
- [ ] Remove static tests from mixed test files
|
- [x] Remove static tests from mixed test files
|
||||||
|
|
||||||
### Step 11: Clean Up Imports and Exports (20 minutes)
|
### Step 11: Clean Up Imports and Exports (20 minutes)
|
||||||
- [ ] Open `ts/proxies/smart-proxy/utils/index.ts`
|
- [x] Open `ts/proxies/smart-proxy/utils/index.ts`
|
||||||
- [ ] Ensure route-helpers.ts is exported
|
- [x] Ensure route-helpers.ts is exported
|
||||||
- [ ] Remove any exports of deleted functions
|
- [x] Remove any exports of deleted functions
|
||||||
- [ ] Open `ts/index.ts`
|
- [x] Open `ts/index.ts`
|
||||||
- [ ] Remove any exports of deleted types/interfaces
|
- [x] Remove any exports of deleted types/interfaces
|
||||||
- [ ] Search for any remaining imports of RedirectHandler or StaticHandler
|
- [x] Search for any remaining imports of RedirectHandler or StaticHandler
|
||||||
- [ ] Remove any found imports
|
- [x] Remove any found imports
|
||||||
|
|
||||||
### Step 12: Documentation Updates (30 minutes)
|
### Step 12: Documentation Updates (30 minutes)
|
||||||
- [ ] Update README.md:
|
- [x] Update README.md:
|
||||||
- [ ] Remove any mention of redirect, block, static action types
|
- [x] Remove any mention of redirect, block, static action types
|
||||||
- [ ] Add examples of socket handlers with context
|
- [x] Add examples of socket handlers with context
|
||||||
- [ ] Document the two action types: forward and socket-handler
|
- [x] Document the two action types: forward and socket-handler
|
||||||
- [ ] Update any JSDoc comments in modified files
|
- [x] Update any JSDoc comments in modified files
|
||||||
- [ ] Add examples showing context usage
|
- [x] Add examples showing context usage
|
||||||
|
|
||||||
### Step 13: Final Verification (15 minutes)
|
### Step 13: Final Verification (15 minutes)
|
||||||
- [ ] Run build: `pnpm build`
|
- [x] Run build: `pnpm build`
|
||||||
- [ ] Fix any compilation errors
|
- [x] Fix any compilation errors
|
||||||
- [ ] Run tests: `pnpm test`
|
- [x] Run tests: `pnpm test`
|
||||||
- [ ] Fix any failing tests
|
- [x] Fix any failing tests
|
||||||
- [ ] Search codebase for any remaining references to:
|
- [x] Search codebase for any remaining references to:
|
||||||
- [ ] 'redirect' action type
|
- [x] 'redirect' action type
|
||||||
- [ ] 'block' action type
|
- [x] 'block' action type
|
||||||
- [ ] 'static' action type
|
- [x] 'static' action type
|
||||||
- [ ] RedirectHandler
|
- [x] RedirectHandler
|
||||||
- [ ] StaticHandler
|
- [x] StaticHandler
|
||||||
- [ ] IRouteRedirect
|
- [x] IRouteRedirect
|
||||||
- [ ] IRouteStatic
|
- [x] IRouteStatic
|
||||||
|
|
||||||
### Step 14: Test New Functionality (30 minutes)
|
### Step 14: Test New Functionality (30 minutes)
|
||||||
- [ ] Create test for block socket handler with context
|
- [x] Create test for block socket handler with context
|
||||||
- [ ] Create test for httpBlock socket handler with context
|
- [x] Create test for httpBlock socket handler with context
|
||||||
- [ ] Create test for httpRedirect socket handler with context
|
- [x] Create test for httpRedirect socket handler with context
|
||||||
- [ ] Verify context is properly passed in all scenarios
|
- [x] Verify context is properly passed in all scenarios
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ import type { IAcmeOptions } from './models/interfaces.js';
|
|||||||
import { CertStore } from './cert-store.js';
|
import { CertStore } from './cert-store.js';
|
||||||
import type { AcmeStateManager } from './acme-state-manager.js';
|
import type { AcmeStateManager } from './acme-state-manager.js';
|
||||||
import { logger } from '../../core/utils/logger.js';
|
import { logger } from '../../core/utils/logger.js';
|
||||||
|
import { SocketHandlers } from './utils/route-helpers.js';
|
||||||
|
|
||||||
export interface ICertStatus {
|
export interface ICertStatus {
|
||||||
domain: string;
|
domain: string;
|
||||||
@ -694,70 +695,53 @@ export class SmartCertManager {
|
|||||||
},
|
},
|
||||||
action: {
|
action: {
|
||||||
type: 'socket-handler',
|
type: 'socket-handler',
|
||||||
socketHandler: (socket, context) => {
|
socketHandler: SocketHandlers.httpServer((req, res) => {
|
||||||
// Wait for HTTP request data
|
// Extract the token from the path
|
||||||
socket.once('data', async (data) => {
|
const token = req.url?.split('/').pop();
|
||||||
const request = data.toString();
|
if (!token) {
|
||||||
const lines = request.split('\r\n');
|
res.status(404);
|
||||||
const [method, path] = lines[0].split(' ');
|
res.send('Not found');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Extract the token from the path
|
// Create mock request/response objects for SmartAcme
|
||||||
const token = path?.split('/').pop();
|
let responseData: any = null;
|
||||||
if (!token) {
|
const mockReq = {
|
||||||
socket.write('HTTP/1.1 404 Not Found\r\n');
|
url: req.url,
|
||||||
socket.write('Content-Type: text/plain\r\n');
|
method: req.method,
|
||||||
socket.write('Content-Length: 9\r\n');
|
headers: req.headers
|
||||||
socket.write('Connection: close\r\n');
|
};
|
||||||
socket.write('\r\n');
|
|
||||||
socket.write('Not found');
|
const mockRes = {
|
||||||
socket.end();
|
statusCode: 200,
|
||||||
return;
|
setHeader: (name: string, value: string) => {},
|
||||||
|
end: (data: any) => {
|
||||||
|
responseData = data;
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Create mock request/response objects for SmartAcme
|
// Use SmartAcme's handler
|
||||||
const mockReq = {
|
const handleAcme = () => {
|
||||||
url: path,
|
http01Handler.handleRequest(mockReq as any, mockRes as any, () => {
|
||||||
method: 'GET',
|
// Not handled by ACME
|
||||||
headers: {}
|
res.status(404);
|
||||||
};
|
res.send('Not found');
|
||||||
|
|
||||||
let responseData: any = null;
|
|
||||||
const mockRes = {
|
|
||||||
statusCode: 200,
|
|
||||||
setHeader: (name: string, value: string) => {},
|
|
||||||
end: (data: any) => {
|
|
||||||
responseData = data;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Use SmartAcme's handler
|
|
||||||
const handled = await new Promise<boolean>((resolve) => {
|
|
||||||
http01Handler.handleRequest(mockReq as any, mockRes as any, () => {
|
|
||||||
resolve(false);
|
|
||||||
});
|
|
||||||
// Give it a moment to process
|
|
||||||
setTimeout(() => resolve(true), 100);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (handled && responseData) {
|
// Give it a moment to process, then send response
|
||||||
const body = String(responseData);
|
setTimeout(() => {
|
||||||
socket.write(`HTTP/1.1 ${mockRes.statusCode} OK\r\n`);
|
if (responseData) {
|
||||||
socket.write('Content-Type: text/plain\r\n');
|
res.header('Content-Type', 'text/plain');
|
||||||
socket.write(`Content-Length: ${body.length}\r\n`);
|
res.send(String(responseData));
|
||||||
socket.write('Connection: close\r\n');
|
} else {
|
||||||
socket.write('\r\n');
|
res.status(404);
|
||||||
socket.write(body);
|
res.send('Not found');
|
||||||
} else {
|
}
|
||||||
socket.write('HTTP/1.1 404 Not Found\r\n');
|
}, 100);
|
||||||
socket.write('Content-Type: text/plain\r\n');
|
};
|
||||||
socket.write('Content-Length: 9\r\n');
|
|
||||||
socket.write('Connection: close\r\n');
|
handleAcme();
|
||||||
socket.write('\r\n');
|
})
|
||||||
socket.write('Not found');
|
|
||||||
}
|
|
||||||
socket.end();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -916,5 +916,115 @@ export const SocketHandlers = {
|
|||||||
socket.write(response);
|
socket.write(response);
|
||||||
socket.end();
|
socket.end();
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTTP server handler for ACME challenges and other HTTP needs
|
||||||
|
*/
|
||||||
|
httpServer: (handler: (req: { method: string; url: string; headers: Record<string, string>; body?: string }, res: { status: (code: number) => void; header: (name: string, value: string) => void; send: (data: string) => void; end: () => void }) => void) => (socket: plugins.net.Socket, context: IRouteContext) => {
|
||||||
|
let buffer = '';
|
||||||
|
let requestParsed = false;
|
||||||
|
|
||||||
|
socket.on('data', (data) => {
|
||||||
|
if (requestParsed) return; // Only handle the first request
|
||||||
|
|
||||||
|
buffer += data.toString();
|
||||||
|
|
||||||
|
// Check if we have a complete HTTP request
|
||||||
|
const headerEndIndex = buffer.indexOf('\r\n\r\n');
|
||||||
|
if (headerEndIndex === -1) return; // Need more data
|
||||||
|
|
||||||
|
requestParsed = true;
|
||||||
|
|
||||||
|
// Parse the HTTP request
|
||||||
|
const headerPart = buffer.substring(0, headerEndIndex);
|
||||||
|
const bodyPart = buffer.substring(headerEndIndex + 4);
|
||||||
|
|
||||||
|
const lines = headerPart.split('\r\n');
|
||||||
|
const [method, url] = lines[0].split(' ');
|
||||||
|
|
||||||
|
const headers: Record<string, string> = {};
|
||||||
|
for (let i = 1; i < lines.length; i++) {
|
||||||
|
const colonIndex = lines[i].indexOf(':');
|
||||||
|
if (colonIndex > 0) {
|
||||||
|
const name = lines[i].substring(0, colonIndex).trim().toLowerCase();
|
||||||
|
const value = lines[i].substring(colonIndex + 1).trim();
|
||||||
|
headers[name] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create request object
|
||||||
|
const req = {
|
||||||
|
method: method || 'GET',
|
||||||
|
url: url || '/',
|
||||||
|
headers,
|
||||||
|
body: bodyPart
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create response object
|
||||||
|
let statusCode = 200;
|
||||||
|
const responseHeaders: Record<string, string> = {};
|
||||||
|
let ended = false;
|
||||||
|
|
||||||
|
const res = {
|
||||||
|
status: (code: number) => {
|
||||||
|
statusCode = code;
|
||||||
|
},
|
||||||
|
header: (name: string, value: string) => {
|
||||||
|
responseHeaders[name] = value;
|
||||||
|
},
|
||||||
|
send: (data: string) => {
|
||||||
|
if (ended) return;
|
||||||
|
ended = true;
|
||||||
|
|
||||||
|
if (!responseHeaders['content-type']) {
|
||||||
|
responseHeaders['content-type'] = 'text/plain';
|
||||||
|
}
|
||||||
|
responseHeaders['content-length'] = String(data.length);
|
||||||
|
responseHeaders['connection'] = 'close';
|
||||||
|
|
||||||
|
const statusText = statusCode === 200 ? 'OK' :
|
||||||
|
statusCode === 404 ? 'Not Found' :
|
||||||
|
statusCode === 500 ? 'Internal Server Error' : 'Response';
|
||||||
|
|
||||||
|
let response = `HTTP/1.1 ${statusCode} ${statusText}\r\n`;
|
||||||
|
for (const [name, value] of Object.entries(responseHeaders)) {
|
||||||
|
response += `${name}: ${value}\r\n`;
|
||||||
|
}
|
||||||
|
response += '\r\n';
|
||||||
|
response += data;
|
||||||
|
|
||||||
|
socket.write(response);
|
||||||
|
socket.end();
|
||||||
|
},
|
||||||
|
end: () => {
|
||||||
|
if (ended) return;
|
||||||
|
ended = true;
|
||||||
|
socket.write('HTTP/1.1 200 OK\r\nContent-Length: 0\r\nConnection: close\r\n\r\n');
|
||||||
|
socket.end();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
handler(req, res);
|
||||||
|
// Ensure response is sent even if handler doesn't call send()
|
||||||
|
setTimeout(() => {
|
||||||
|
if (!ended) {
|
||||||
|
res.send('');
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
} catch (error) {
|
||||||
|
if (!ended) {
|
||||||
|
res.status(500);
|
||||||
|
res.send('Internal Server Error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('error', () => {
|
||||||
|
if (!requestParsed) {
|
||||||
|
socket.end();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -74,21 +74,7 @@ export function mergeRouteConfigs(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Merge redirect options
|
// No special merging needed for socket handlers - they are functions
|
||||||
if (overrideRoute.action.redirect) {
|
|
||||||
mergedRoute.action.redirect = {
|
|
||||||
...mergedRoute.action.redirect,
|
|
||||||
...overrideRoute.action.redirect
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Merge static options
|
|
||||||
if (overrideRoute.action.static) {
|
|
||||||
mergedRoute.action.static = {
|
|
||||||
...mergedRoute.action.static,
|
|
||||||
...overrideRoute.action.static
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -143,30 +143,12 @@ export function validateRouteAction(action: IRouteAction): { valid: boolean; err
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate redirect for 'redirect' action
|
// Validate socket handler for 'socket-handler' action
|
||||||
if (action.type === 'redirect') {
|
if (action.type === 'socket-handler') {
|
||||||
if (!action.redirect) {
|
if (!action.socketHandler) {
|
||||||
errors.push('Redirect configuration is required for redirect action');
|
errors.push('Socket handler function is required for socket-handler action');
|
||||||
} else {
|
} else if (typeof action.socketHandler !== 'function') {
|
||||||
if (!action.redirect.to) {
|
errors.push('Socket handler must be a function');
|
||||||
errors.push('Redirect target (to) is required');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (action.redirect.status &&
|
|
||||||
![301, 302, 303, 307, 308].includes(action.redirect.status)) {
|
|
||||||
errors.push('Invalid redirect status code');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate static file config for 'static' action
|
|
||||||
if (action.type === 'static') {
|
|
||||||
if (!action.static) {
|
|
||||||
errors.push('Static file configuration is required for static action');
|
|
||||||
} else {
|
|
||||||
if (!action.static.root) {
|
|
||||||
errors.push('Static file root directory is required');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -261,12 +243,8 @@ export function hasRequiredPropertiesForAction(route: IRouteConfig, actionType:
|
|||||||
switch (actionType) {
|
switch (actionType) {
|
||||||
case 'forward':
|
case 'forward':
|
||||||
return !!route.action.target && !!route.action.target.host && !!route.action.target.port;
|
return !!route.action.target && !!route.action.target.host && !!route.action.target.port;
|
||||||
case 'redirect':
|
case 'socket-handler':
|
||||||
return !!route.action.redirect && !!route.action.redirect.to;
|
return !!route.action.socketHandler && typeof route.action.socketHandler === 'function';
|
||||||
case 'static':
|
|
||||||
return !!route.action.static && !!route.action.static.root;
|
|
||||||
case 'block':
|
|
||||||
return true; // Block action doesn't require additional properties
|
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user