Skip to content

Commit

Permalink
Merge pull request #41 from irensaltali/develop
Browse files Browse the repository at this point in the history
fix: 39 and refactor
  • Loading branch information
irensaltali committed Feb 17, 2024
2 parents f1a40d3 + 4c48dad commit 14d08ec
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 36 deletions.
31 changes: 29 additions & 2 deletions worker/src/api-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@
"servers": [
{
"alias": "serverlessapigateway-api",
"url": "https://api.serverlessapigateway.com"
"url": "https://4e05-2a02-e0-665f-2400-e945-4e3-409c-d532.ngrok-free.app"
},
{
"alias": "serverlessapigateway-api-sub",
"url": "https://4e05-2a02-e0-665f-2400-e945-4e3-409c-d532.ngrok-free.app/sub"
}
],
"cors": {
Expand Down Expand Up @@ -95,13 +99,29 @@
}
},
{
"method": "GET",
"method": "ANY",
"path": "/api/v1/proxy/{.+}",
"integration": {
"type": "http_proxy",
"server": "serverlessapigateway-api"
}
},
{
"method": "ANY",
"path": "/{.+}",
"integration": {
"type": "http_proxy",
"server": "serverlessapigateway-api"
}
},
{
"method": "GET",
"path": "/api/v1/proxy/sub",
"integration": {
"type": "http",
"server": "serverlessapigateway-api-sub"
}
},
{
"method": "GET",
"path": "/api/v1/method",
Expand Down Expand Up @@ -170,6 +190,13 @@
"response": {
"status": "ok"
}
},
{
"method": "ANY",
"path": "/api/v1/health/any",
"response": {
"status": "ok"
}
}
]
}
52 changes: 40 additions & 12 deletions worker/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,35 +11,63 @@ export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
const url = new URL(request.url);

// Handle CORS preflight (OPTIONS) requests first
if (apiConfig.cors && request.method === 'OPTIONS' && !apiConfig.paths.find(item => item.method === 'OPTIONS' && pathsMatch(item.path, url.pathname))) {
return setPoweredByHeader(setCorsHeaders(request, new Response(null, { status: 204 })));
// Handle CORS preflight (OPTIONS) requests directly
if (apiConfig.cors && request.method === 'OPTIONS') {
const matchedItem = apiConfig.paths.find(item => {
const matchResult = pathsMatch(item.path, url.pathname, request.method, item.method);
return item.method === 'OPTIONS' && matchResult?.matchedCount > 0 && matchResult.methodMatches;
});
if (!matchedItem) {
console.log('Handling CORS preflight request');
return setPoweredByHeader(setCorsHeaders(request, new Response(null, { status: 204 })));
}
}


// Filter paths based on URL match and select the one with the most matched segments
// Adjusted filtering based on the updated pathsMatch return value
const matchedPaths = apiConfig.paths
.map(item => ({ ...item, matchLength: pathsMatch(item.path, url.pathname) }))
.filter(item => item.matchLength > 0);
.map(config => ({ ...config, matchResult: pathsMatch(config.path, url.pathname, request.method, config.method) }))
.filter(item => item.matchResult.matchedCount > 0 && item.matchResult.methodMatches); // Only consider matches with the correct method

console.log('Matched paths:', matchedPaths);

// Sorting with priority: exact matches > parameterized matches > wildcard matches
const matchedPath = matchedPaths.sort((a, b) => {
// Prioritize exact matches
if (a.matchResult.isExact !== b.matchResult.isExact) {
return a.matchResult.isExact ? -1 : 1;
}
// Among exact or parameterized matches, prioritize those with more matched segments
if (a.matchResult.matchedCount !== b.matchResult.matchedCount) {
return b.matchResult.matchedCount - a.matchResult.matchedCount;
}
// If both are parameterized, prioritize non-wildcard over wildcard
if (a.matchResult.isWildcard !== b.matchResult.isWildcard) {
return a.matchResult.isWildcard ? 1 : -1;
}
// Prioritize exact method matches over "ANY"
if (a.method !== b.method) {
if (a.method === request.method) return -1;
if (b.method === request.method) return 1;
}
return 0; // Equal priority
})[0];

// Find the matched path with the most segments matching
const matchedPath = matchedPaths.sort((a, b) => b.matchLength - a.matchLength)[0];
console.log('Matched path:', matchedPath);

if (matchedPath) {
var jwtPayload = {};
if (apiConfig.authorizer && matchedPath.auth) {
jwtPayload = await jwtAuth(request);
if (!jwtPayload.iss) {
return setPoweredByHeader(setCorsHeaders(request, responses.unauthorizedResponse));
return setPoweredByHeader(setCorsHeaders(request, responses.unauthorizedResponse()));
}
}

if (matchedPath.integration && matchedPath.integration.type.includes('http')) {
const server = apiConfig.servers.find(server => server.alias === matchedPath.integration.server);
if (server) {
var modifiedRequest = createProxiedRequest(request, server, matchedPath);
console.log('Modified request:', modifiedRequest);
// console.log('Modified request:', modifiedRequest);
if (matchedPath.mapping) {
console.log('Applying mapping:', matchedPath.mapping);
modifiedRequest = await applyValueMapping(modifiedRequest, matchedPath.mapping, jwtPayload, matchedPath.variables);
Expand All @@ -51,6 +79,6 @@ export default {
}
}

return setPoweredByHeader(setCorsHeaders(request, responses.noMatchResponse));
return setPoweredByHeader(setCorsHeaders(request, responses.noMatchResponse()));
}
};
62 changes: 42 additions & 20 deletions worker/src/path-ops.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,33 +9,55 @@ function isWildcard(segment: string): boolean {
}

// Function to match the paths and return the count of matched segments
function pathsMatch(apiPath: string, urlPath: string): number {
const apiSegments = apiPath.split('/');
const urlSegments = urlPath.split('/');
function pathsMatch(configPath: string, requestPath: string, requestMethod: string, configMethod: string): { matchedCount: number, isExact: boolean, isWildcard: boolean, methodMatches: boolean } {
const configSegments = configPath.split('/');
const requestSegments = requestPath.split('/');
let matchedSegments = 0;
let isExact = true;
let isWildcardUsed = false;
// Method match check
const methodMatches = requestMethod === configMethod || configMethod === 'ANY';

for (let i = 0; i < apiSegments.length; i++) {
if (isWildcard(apiSegments[i])) {
return apiSegments.length; // Wildcard matches all remaining segments
}
if (!methodMatches) {
return { matchedCount: 0, isExact: false, isWildcard: false, methodMatches: methodMatches };
}

if (i >= urlSegments.length) {
break; // No more URL segments to compare
}
if (!isWildcard(configSegments[configSegments.length - 1]) && requestSegments.length !== configSegments.length) {
return { matchedCount: 0, isExact: false, isWildcard: false, methodMatches: methodMatches };
}

if (isWildcard(configSegments[configSegments.length - 1]) && requestSegments.length < configSegments.length - 1) {
return { matchedCount: 0, isExact: false, isWildcard: false, methodMatches: methodMatches };
}

if (!isPathParam(apiSegments[i]) && apiSegments[i] !== urlSegments[i]) {
break; // Segment mismatch
for (let i = 0; i < Math.max(configSegments.length, requestSegments.length); i++) {
if (i < configSegments.length && isWildcard(configSegments[i])) {
isWildcardUsed = true;
matchedSegments = Math.min(configSegments.length, requestSegments.length); // Wildcard matches all corresponding segments
break;
}

matchedSegments++;
}
if (i >= configSegments.length || i >= requestSegments.length) {
isExact = false;
break; // Reached the end of one of the paths
}

// Check if URL segments exactly match API path segments
if (matchedSegments === apiSegments.length && matchedSegments === urlSegments.length) {
return matchedSegments;
if (isPathParam(configSegments[i])) {
isExact = false; // Found a parameterized segment, so it's not an exact match
matchedSegments++;
} else if (configSegments[i] === requestSegments[i]) {
matchedSegments++; // Exact match for this segment
} else {
return { matchedCount: 0, isExact: false, isWildcard: false, methodMatches: methodMatches }; // Mismatch found
}
}

return 0; // Mismatch or partial match
return {
matchedCount: matchedSegments,
isExact: isExact && matchedSegments === configSegments.length && matchedSegments === requestSegments.length,
isWildcard: isWildcardUsed,
methodMatches: methodMatches // Include method match status
};
}

// Define types for server and path
Expand All @@ -61,13 +83,13 @@ function createProxiedRequest(request: Request, server: Server, matchedPath: Pat

if (matchedPath.integration.type === 'http_proxy') {
// For 'http_proxy', use the original path without the matching part
const matchedPathPart = matchedPath.path.replace('{.+}', '');
const matchedPathPart = matchedPath.path.replace('{.+}', '');
newPath = requestUrl.pathname.replace(matchedPathPart, '/');
console.log('New path:', newPath);
}

// Create the new request with the updated URL
const newRequest =new Request(server.url + newPath + requestUrl.search, request);
const newRequest = new Request(server.url + newPath + requestUrl.search, request);
return newRequest;
}

Expand Down
4 changes: 2 additions & 2 deletions worker/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
/* Language and Environment */
"target": "es2021" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
"lib": [
"lib": ["es5",
"es2015"
] /* Specify a set of bundled library declaration files that describe the target runtime environment. */,

/* Language and Environment */
Expand Down

0 comments on commit 14d08ec

Please sign in to comment.