Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow binding websocketPort to port #1799

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 17 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,14 @@ Default: 600 (10 minutes)
WebSocket port to listen on.<br />
Default: 3001

The `websocketPort` may also be set to the same as `port` so that a single port can be used for mulitple protocols.

Note: In the event the ports are the same:

- The [Connections API Server](#websocket-connections-api) will be hosted on `lambdaPort`.

### CLI Options in `serverless.yml`

Any of the CLI options can be added to your `serverless.yml`. For example:

```yml
Expand Down Expand Up @@ -722,20 +730,25 @@ Example response velocity template:
},
```

## WebSocket
## WebSocket Connections API

Usage in order to send messages back to clients:
The `connections-port` for the connections API is available at the following endpoint:

`POST http://localhost:3001/@connections/{connectionId}`
- if `websocketPort == 3001`: (connections API and websocket share `websocketPort`)
- `POST http://localhost:3001/@connections/{connectionId}`
- if `websocketPort == port`: (connections API is bound to the `lambdaPort`)
- `POST http://localhost:3002/@connections/{connectionId}`

Or,

```js
import aws from 'aws-sdk'

const connectionsPort = 3001; // Or 3002 if websocketPort === port in serverless offline options

const apiGatewayManagementApi = new aws.ApiGatewayManagementApi({
apiVersion: '2018-11-29',
endpoint: 'http://localhost:3001',
endpoint: `http://localhost:${connectionsPort}`,
});

apiGatewayManagementApi.postToConnection({
Expand Down
52 changes: 9 additions & 43 deletions src/events/alb/HttpServer.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import { Buffer } from "node:buffer"
import { exit } from "node:process"
import { Server } from "@hapi/hapi"
import { log } from "../../utils/log.js"
import {
detectEncoding,
Expand All @@ -9,43 +7,30 @@ import {
} from "../../utils/index.js"
import LambdaAlbRequestEvent from "./lambda-events/LambdaAlbRequestEvent.js"
import logRoutes from "../../utils/logRoutes.js"
import AbstractHttpServer from "../../lambda/AbstractHttpServer.js"

const { stringify } = JSON
const { entries } = Object

export default class HttpServer {
export default class HttpServer extends AbstractHttpServer {
#lambda = null

#options = null

#serverless = null

#server = null

#terminalInfo = []

constructor(serverless, options, lambda) {
super(lambda, options, options.albPort)

this.#serverless = serverless
this.#options = options
this.#lambda = lambda
}

async createServer() {
const { host, albPort } = this.#options

const serverOptions = {
host,
port: albPort,
router: {
// allows for paths with trailing slashes to be the same as without
// e.g. : /my-path is the same as /my-path/
stripTrailingSlash: true,
},
}

this.#server = new Server(serverOptions)

this.#server.ext("onPreResponse", (request, h) => {
this.httpServer.ext("onPreResponse", (request, h) => {
if (request.headers.origin) {
const response = request.response.isBoom
? request.response.output
Expand Down Expand Up @@ -134,32 +119,13 @@ export default class HttpServer {
}

async start() {
const { albPort, host, httpsProtocol } = this.#options

try {
await this.#server.start()
} catch (err) {
log.error(
`Unexpected error while starting serverless-offline alb server on port ${albPort}:`,
err,
)
exit(1)
}

// TODO move the following block
const server = `${httpsProtocol ? "https" : "http"}://${host}:${albPort}`
await super.start()

log.notice(`ALB Server ready: ${server} 🚀`)
}

stop(timeout) {
return this.#server.stop({
timeout,
})
log.notice(`${this.serverName} Server ready: ${this.basePath} 🚀`)
}

get server() {
return this.#server.listener
return this.httpServer.listener
}

#createHapiHandler(params) {
Expand Down Expand Up @@ -346,7 +312,7 @@ export default class HttpServer {
stage,
})

this.#server.route({
this.httpServer.route({
handler: hapiHandler,
method: hapiMethod,
options: hapiOptions,
Expand Down
103 changes: 20 additions & 83 deletions src/events/http/HttpServer.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import { Buffer } from "node:buffer"
import { readFile } from "node:fs/promises"

import { createRequire } from "node:module"
import { join, resolve } from "node:path"
import { exit } from "node:process"
import h2o2 from "@hapi/h2o2"
import { Server } from "@hapi/hapi"
import { log } from "../../utils/log.js"
import authFunctionNameExtractor from "../authFunctionNameExtractor.js"
import authJWTSettingsExtractor from "./authJWTSettingsExtractor.js"
Expand All @@ -30,11 +28,12 @@ import {
jsonPath,
splitHandlerPathAndName,
} from "../../utils/index.js"
import AbstractHttpServer from "../../lambda/AbstractHttpServer.js"

const { parse, stringify } = JSON
const { assign, entries, keys } = Object

export default class HttpServer {
export default class HttpServer extends AbstractHttpServer {
#apiKeysValues = null

#hasPrivateHttpEvent = false
Expand All @@ -43,68 +42,26 @@ export default class HttpServer {

#options = null

#server = null

#serverless = null

#terminalInfo = []

constructor(serverless, options, lambda) {
super(lambda, options, options.httpPort)
this.#lambda = lambda
this.#options = options
this.#serverless = serverless
}

async #loadCerts(httpsProtocol) {
const [cert, key] = await Promise.all([
readFile(resolve(httpsProtocol, "cert.pem"), "utf8"),
readFile(resolve(httpsProtocol, "key.pem"), "utf8"),
])

return {
cert,
key,
}
}

async createServer() {
const { enforceSecureCookies, host, httpPort, httpsProtocol } =
this.#options

const serverOptions = {
host,
port: httpPort,
router: {
stripTrailingSlash: true,
},
state: enforceSecureCookies
? {
isHttpOnly: true,
isSameSite: false,
isSecure: true,
}
: {
isHttpOnly: false,
isSameSite: false,
isSecure: false,
},
// https support
...(httpsProtocol != null && {
tls: await this.#loadCerts(httpsProtocol),
}),
}

// Hapijs server creation
this.#server = new Server(serverOptions)

try {
await this.#server.register([h2o2])
await this.httpServer.register([h2o2])
} catch (err) {
log.error(err)
}

// Enable CORS preflight response
this.#server.ext("onPreResponse", (request, h) => {
this.httpServer.ext("onPreResponse", (request, h) => {
if (request.headers.origin) {
const response = request.response.isBoom
? request.response.output
Expand Down Expand Up @@ -193,29 +150,9 @@ export default class HttpServer {
}

async start() {
const { host, httpPort, httpsProtocol } = this.#options
await super.start()

try {
await this.#server.start()
} catch (err) {
log.error(
`Unexpected error while starting serverless-offline server on port ${httpPort}:`,
err,
)
exit(1)
}

// TODO move the following block
const server = `${httpsProtocol ? "https" : "http"}://${host}:${httpPort}`

log.notice(`Server ready: ${server} 🚀`)
}

// stops the server
stop(timeout) {
return this.#server.stop({
timeout,
})
log.notice(`Server ready: ${this.basePath} 🚀`)
}

#logPluginIssue() {
Expand Down Expand Up @@ -278,8 +215,8 @@ export default class HttpServer {
const scheme = createJWTAuthScheme(jwtSettings)

// Set the auth scheme and strategy on the server
this.#server.auth.scheme(authSchemeName, scheme)
this.#server.auth.strategy(authStrategyName, authSchemeName)
this.httpServer.auth.scheme(authSchemeName, scheme)
this.httpServer.auth.strategy(authStrategyName, authSchemeName)

return authStrategyName
}
Expand Down Expand Up @@ -387,8 +324,8 @@ export default class HttpServer {
)

// Set the auth scheme and strategy on the server
this.#server.auth.scheme(authSchemeName, scheme)
this.#server.auth.strategy(authStrategyName, authSchemeName)
this.httpServer.auth.scheme(authSchemeName, scheme)
this.httpServer.auth.strategy(authStrategyName, authSchemeName)

return authStrategyName
}
Expand Down Expand Up @@ -416,11 +353,11 @@ export default class HttpServer {

const strategy = provider(endpoint, functionKey, method, path)

this.#server.auth.scheme(
this.httpServer.auth.scheme(
strategy.scheme,
strategy.getAuthenticateFunction,
)
this.#server.auth.strategy(strategy.name, strategy.scheme)
this.httpServer.auth.strategy(strategy.name, strategy.scheme)

return strategy.name
}
Expand Down Expand Up @@ -1118,7 +1055,7 @@ export default class HttpServer {
stage,
})

this.#server.route({
this.httpServer.route({
handler: hapiHandler,
method: hapiMethod,
options: hapiOptions,
Expand Down Expand Up @@ -1267,17 +1204,17 @@ export default class HttpServer {
path: hapiPath,
}

this.#server.route(route)
this.httpServer.route(route)
})
}

create404Route() {
// If a {proxy+} or $default route exists, don't conflict with it
if (this.#server.match("*", "/{p*}")) {
if (this.httpServer.match("*", "/{p*}")) {
return
}

const existingRoutes = this.#server
const existingRoutes = this.httpServer
.table()
// Exclude this (404) route
.filter((route) => route.path !== "/{p*}")
Expand Down Expand Up @@ -1305,7 +1242,7 @@ export default class HttpServer {
path: "/{p*}",
}

this.#server.route(route)
this.httpServer.route(route)
}

#getArrayStackTrace(stack) {
Expand All @@ -1329,6 +1266,6 @@ export default class HttpServer {

// TEMP FIXME quick fix to expose gateway server for testing, look for better solution
getServer() {
return this.#server
return this.httpServer
}
}
Loading
Loading