@wix/be-server
Advanced tools
Comparing version 1.0.5 to 1.0.6
const {serverBuilder} = require('./lib/server-builder'); | ||
const protoModulesBindings = require('./lib/proto-modules-bindings'); | ||
module.exports = { | ||
builder: serverBuilder | ||
builder: serverBuilder, | ||
fromProtoModules: protoModulesBindings | ||
}; |
const url = require('url'); | ||
const UrlPattern = require('url-pattern'); | ||
const {resolveHttpRoutes} = require('@wix/be-http-binding'); | ||
const querystring = require('querystring'); | ||
module.exports = function routes(services, messageTypes) { | ||
module.exports = function routes(bindings) { | ||
const routes = {}; | ||
services.forEach(({service, bindings = {}}) => { | ||
const serviceRoutes = resolveHttpRoutes(service); | ||
bindings.forEach(({binding, invoke}) => { | ||
binding.httpRoutes().forEach(({method, path}) => { | ||
const httpMethodLower = method.toLowerCase(); | ||
if (!routes[httpMethodLower]) { | ||
routes[httpMethodLower] = []; | ||
} | ||
Object.keys(serviceRoutes).forEach((methodName) => { | ||
const methodRoutes = serviceRoutes[methodName]; | ||
const method = service.methods[methodName]; | ||
const requestMessage = messageTypes.lookup(method.parent, method.requestType); | ||
Object.keys(methodRoutes).forEach((httpMethod) => { | ||
const httpMethodLower = httpMethod.toLowerCase(); | ||
if (!routes[httpMethodLower]) { | ||
routes[httpMethodLower] = []; | ||
} | ||
methodRoutes[httpMethod].forEach((path) => { | ||
routes[httpMethodLower].push({ | ||
pattern: new UrlPattern(fromCurly(path)), | ||
method, | ||
requestMessage, | ||
implementation: bindings[methodName] | ||
}); | ||
}); | ||
routes[httpMethodLower].push({ | ||
pattern: new UrlPattern(fromCurly(path)), | ||
method, | ||
invoke: binding.createInvoke(invoke) | ||
}); | ||
}); | ||
}) | ||
}); | ||
return { | ||
resolve(httpMethod, uri) { | ||
const parsedUri = url.parse(uri); | ||
const methodsForHttpMethod = routes[httpMethod.toLowerCase()]; | ||
if (!methodsForHttpMethod) { | ||
return; | ||
return {}; | ||
} | ||
let resolvedRoute; | ||
let request; | ||
let request = {}; | ||
for (let i = 0; i < methodsForHttpMethod.length; i++) { | ||
@@ -59,3 +46,3 @@ request = methodsForHttpMethod[i].pattern.match(parsedUri.pathname); | ||
if (parsedUri.query > '') { | ||
if (request && parsedUri.query > '') { | ||
const query = querystring.parse(parsedUri.query); | ||
@@ -69,3 +56,3 @@ | ||
return { | ||
route: resolvedRoute, | ||
invoke: resolvedRoute ? resolvedRoute.invoke : null, | ||
request | ||
@@ -72,0 +59,0 @@ }; |
const routes = require('./routes'); | ||
const messageTypes = require('./message-types'); | ||
const {expect} = require('chai'); | ||
const protobufjs = require('protobufjs'); | ||
const {httpBinding, messageBuilder, wellKnownTypes} = require('@wix/be-http-binding'); | ||
const {http, get} = httpBinding; | ||
describe('Routes', () => { | ||
it('should match and resolve values in URI', () => { | ||
const givenRoutes = routesFrom(` | ||
service TestRoutes { | ||
rpc Get (Message) returns (Message) { | ||
option (google.api.http) = { | ||
get: "/api/{valueFromPath}" | ||
}; | ||
} | ||
} | ||
const echoMessage = messageBuilder().field('message', wellKnownTypes.string, 1).build(); | ||
message Message { | ||
string value_from_path = 1; | ||
string value_from_query = 2; | ||
} | ||
`); | ||
it('should match a GET route', async() => { | ||
const givenBinding = http(get('/echo'), echoMessage, echoMessage); | ||
const givenRoutes = routes([{ | ||
binding: givenBinding, | ||
invoke: (message) => message | ||
}]); | ||
const {request, route} = givenRoutes.resolve('GET', '/api/path-1?valueFromQuery=query-1'); | ||
const {request, invoke} = givenRoutes.resolve('GET', '/echo?message=Hello'); | ||
expect(route.method.name).to.equal('Get'); | ||
expect(request).to.deep.equal({ | ||
valueFromPath: 'path-1', | ||
valueFromQuery: 'query-1' | ||
}); | ||
expect(request).to.deep.equal({ | ||
message: 'Hello' | ||
}); | ||
expect(invoke).to.exist; | ||
expect(await invoke(request)).to.deep.equal(request); | ||
}); | ||
it('should match and resolve values in URI', async() => { | ||
const givenMessage = messageBuilder() | ||
.field('valueFromPath', wellKnownTypes.string, 1) | ||
.field('valueFromQuery', wellKnownTypes.string, 2) | ||
.build(); | ||
const givenBinding = http(get('/api/{valueFromPath}'), givenMessage, givenMessage); | ||
const givenRoutes = routes([{ | ||
binding: givenBinding, | ||
invoke: (message) => message | ||
}]); | ||
const {request, invoke} = givenRoutes.resolve('GET', '/api/path-1?valueFromQuery=query-1'); | ||
expect(request).to.deep.equal({ | ||
valueFromPath: 'path-1', | ||
valueFromQuery: 'query-1' | ||
}); | ||
expect(invoke).to.exist; | ||
expect(await invoke(request)).to.deep.equal(request); | ||
}); | ||
it('should extract complex request arguments', () => { | ||
const givenRoutes = routesFrom(` | ||
service TestRoutes { | ||
rpc Get (Empty) returns (Empty) { | ||
option (google.api.http) = { | ||
get: "/api/{valueFromPath}" | ||
}; | ||
} | ||
} | ||
const givenMessage = messageBuilder().build(); | ||
message Empty {} | ||
`); | ||
const givenBinding = http(get('/api/{valueFromPath}'), givenMessage, givenMessage); | ||
const {request, route} = givenRoutes.resolve('GET', '/api/path-1?test.arr=arr-1&test.arr=arr-2&test.bool=true&num=10'); | ||
const givenRoutes = routes([{ | ||
binding: givenBinding, | ||
invoke: (message) => message | ||
}]); | ||
expect(route.method.name).to.equal('Get'); | ||
const {request, invoke} = givenRoutes.resolve('GET', '/api/path-1?test.arr=arr-1&test.arr=arr-2&test.bool=true&num=10'); | ||
expect(invoke).to.exist; | ||
expect(request).to.deep.equal({ | ||
@@ -58,20 +73,2 @@ test: { | ||
}); | ||
function routesFrom(source) { | ||
const parsed = protobufjs.parse(` | ||
syntax = "proto3"; | ||
package test; | ||
import "google/api/http.proto"; | ||
${source} | ||
`); | ||
const ns = parsed.root.nested.test; | ||
return routes(Object.values(ns.nested).filter((type) => type instanceof protobufjs.Service).map((service) => ({ | ||
service | ||
})), messageTypes(parsed.root)); | ||
} | ||
}); |
@@ -1,5 +0,2 @@ | ||
const {create} = require('@wix/proto-packages'); | ||
const startServer = require('./server'); | ||
const defaultContextDir = require('find-root')(process.argv[1]); | ||
const messageTypes = require('./message-types'); | ||
@@ -12,52 +9,23 @@ module.exports = { | ||
return { | ||
withContextDir(contextDir) { | ||
withBindings(bindings) { | ||
return serverBuilder({ | ||
...context, | ||
contextDir | ||
bindings | ||
}); | ||
}, | ||
withExtraProtoPackage(extraPackageDir) { | ||
withBindingsSource(bindingsSource) { | ||
return serverBuilder({ | ||
...context, | ||
extraPackageDir | ||
bindingsSource | ||
}); | ||
}, | ||
withService(serviceType, implementation) { | ||
const services = {...(context.services || {})}; | ||
services[serviceType] = implementation; | ||
context.services = services; | ||
return serverBuilder({ | ||
...context | ||
}); | ||
}, | ||
async start(options) { | ||
const protoContext = create({ | ||
contextDir: context.contextDir || defaultContextDir, | ||
sourceRoots: ['proto'], | ||
extraPackages: [context.extraPackageDir] | ||
}); | ||
const sourcedBindings = context.bindingsSource ? await context.bindingsSource.bindings() : []; | ||
const dynamicBindings = context.bindings; | ||
const bindings = [...sourcedBindings, ...dynamicBindings]; | ||
const loadedContext = await protoContext.loadedContext(); | ||
const services = Object.keys(context.services).map((serviceName) => { | ||
const service = loadedContext.lookupService(serviceName); | ||
const bindings = {}; | ||
Object.values(service.methods).forEach((method) => { | ||
bindings[method.name] = context.services[serviceName][toSmallCap(method.name)]; | ||
}); | ||
return { | ||
service, | ||
bindings | ||
}; | ||
}); | ||
return startServer({ | ||
...options, | ||
services, | ||
messageTypes: messageTypes(loadedContext) | ||
bindings | ||
}); | ||
@@ -67,5 +35,1 @@ } | ||
} | ||
function toSmallCap(value) { | ||
return value.charAt(0).toLowerCase() + value.slice(1); | ||
} |
@@ -5,25 +5,25 @@ const http = require('http'); | ||
module.exports = function start(options) { | ||
const methodRoutes = routes(options.services, options.messageTypes); | ||
const methodRoutes = routes(options.bindings); | ||
const server = http.createServer(async(req, res) => { | ||
try { | ||
const {route, request} = methodRoutes.resolve(req.method, req.url); | ||
const {invoke, request} = methodRoutes.resolve(req.method, req.url); | ||
if (!route) { | ||
if (!invoke) { | ||
res.statusCode = 404; | ||
} | ||
} else { | ||
const contentType = req.headers['content-type']; | ||
const contentType = req.headers['content-type']; | ||
if (contentType) { | ||
await new Promise((resolve, reject) => | ||
req.on('data', async(data) => { | ||
try { | ||
resolve(await execute(route, res, request, JSON.parse(data))); | ||
} catch(e) { | ||
reject(e); | ||
} | ||
})); | ||
} else { | ||
await execute(route, res, request); | ||
if (contentType) { | ||
await new Promise((resolve, reject) => | ||
req.on('data', async(data) => { | ||
try { | ||
resolve(await execute(invoke, res, request, JSON.parse(data))); | ||
} catch(e) { | ||
reject(e); | ||
} | ||
})); | ||
} else { | ||
await execute(invoke, res, request); | ||
} | ||
} | ||
@@ -52,11 +52,7 @@ } catch(e) { | ||
async function execute(route, res, request, body = {}) { | ||
const requestMessage = route.requestMessage; | ||
async function execute(invoke, res, request, body = {}) { | ||
const result = await invoke([request, body]); | ||
const message = requestMessage.fromValue([request, body]); | ||
const result = await route.implementation(message); | ||
res.setHeader('Content-Type', 'application/json'); | ||
res.write(JSON.stringify(result)); | ||
} |
{ | ||
"name": "@wix/be-server", | ||
"version": "1.0.5", | ||
"version": "1.0.6", | ||
"author": "Mantas Indrašius <mantasi@wix.com>", | ||
@@ -5,0 +5,0 @@ "publishConfig": { |
@@ -5,2 +5,4 @@ const beServer = require('..'); | ||
const path = require('path'); | ||
const {httpBinding, messageBuilder, wellKnownTypes} = require('@wix/be-http-binding'); | ||
const {http, get} = httpBinding; | ||
@@ -13,11 +15,20 @@ describe('HTTP server', function() { | ||
const echoMessage = messageBuilder().field('message', wellKnownTypes.string, 1).build(); | ||
before(async() => { | ||
server = await beServer.builder() | ||
.withContextDir(path.resolve(__dirname, '..')) | ||
.withExtraProtoPackage(path.resolve(__dirname, 'proto')) | ||
.withService('test.EchoService', { | ||
echo: (message) => message, | ||
postEcho: (message) => message, | ||
typesEcho: (message) => message, | ||
}) | ||
.withBindingsSource(beServer.fromProtoModules({ | ||
contextDir: path.resolve(__dirname, '..'), | ||
extraPackages: [path.resolve(__dirname, 'proto')] | ||
}, { | ||
'test.EchoService': { | ||
echo: (message) => message, | ||
postEcho: (message) => message, | ||
typesEcho: (message) => message, | ||
} | ||
})) | ||
.withBindings([{ | ||
binding: http(get('/api/dynamic-echo'), echoMessage, echoMessage), | ||
invoke: (message) => message | ||
}]) | ||
.start({ port: 9901 }); | ||
@@ -28,3 +39,3 @@ }); | ||
it('should call to an exposed endpoint', async() => { | ||
it('should call an exposed endpoint', async() => { | ||
const response = await fetch('http://localhost:9901/api/echo?message=Hello'); | ||
@@ -41,2 +52,14 @@ | ||
it('should call a dynamic endpoint', async() => { | ||
const response = await fetch('http://localhost:9901/api/dynamic-echo?message=Hello'); | ||
expect(response.status).to.equal(200); | ||
const body = await response.json(); | ||
expect(body).to.deep.equal({ | ||
message: 'Hello' | ||
}); | ||
}); | ||
it('should post', async() => { | ||
@@ -43,0 +66,0 @@ const response = await fetch('http://localhost:9901/api/echo', { |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
15293
13
406
5