feat(core): https servers support
ADD
assets/cert.pem
+-----BEGIN CERTIFICATE-----
+MIIDlDCCAnwCCQD2/UZrTGhrsDANBgkqhkiG9w0BAQUFADCBizELMAkGA1UEBhMC
+UEwxEDAOBgNVBAgMB1NpbGVzaWExDTALBgNVBAcMBGNpdHkxDzANBgNVBAoMBk1h
+cmJsZTEQMA4GA1UECwwHc2VjdGlvbjEPMA0GA1UEAwwGbWFyYmxlMScwJQYJKoZI
+hvcNAQkBFhhqb3plZi5mbGFrdXNAamZsYWt1cy5jb20wHhcNMTkwMjE3MTYxOTE5
+WhcNMTkwMzE5MTYxOTE5WjCBizELMAkGA1UEBhMCUEwxEDAOBgNVBAgMB1NpbGVz
+aWExDTALBgNVBAcMBGNpdHkxDzANBgNVBAoMBk1hcmJsZTEQMA4GA1UECwwHc2Vj
+dGlvbjEPMA0GA1UEAwwGbWFyYmxlMScwJQYJKoZIhvcNAQkBFhhqb3plZi5mbGFr
+dXNAamZsYWt1cy5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCi
+vRsY4oDz1DX9l0cuy2Dgn/mk27ku4afaqhp7oAlHjoeVsbFAdPLqHRq735BwwXqa
+5I59Pbq7EBSS+x144eLqm7yVB5QqqSTQUYdl90fOcFI8AarNPXADlaMh23RERGVK
+9yHPK9KoqglgiBcdOsb9FX1nauidje6O1WHkmOxLV10lIgH8xwODxySmN73UFH+3
+xEYPfSq0bm1ExFNqj+QECEyWetwaTNHFXhDm9oVkuOZiSuxgogKbN074H5zzO4I8
+HKwlaPbYxeEhP4Jtv5AJQpRJ+XApWQ21ZthiJUzDomVayrl1OMKNsr4vLqVlOK9X
+nq4MkDAKgtojsm3/AOZPAgMBAAEwDQYJKoZIhvcNAQEFBQADggEBAJXWhbfOR6OV
+mcpFW7YQnMYkzKvFPBSNO9TCyFptrG3EDLBV/FL/aARq9exjNk3+UG0YIoJggcvm
+GpqQofBK13K5ADrXNj/ldSVVagrKGqDzJ8IySGA8UTW3QyFgH2cHy5bYwvZdYMQg
+XVL7FI8Q1oz/SYEYn+4ZCmukC5rLqO1qQM7BTv5/hPKTe6mo1tk5g7FgMrNUyXeZ
+14sXn/IyNQz9eoPliGE3ANxjUxPs6TLYWY9F4IgZpg+qQlT/VjrckYVYer/gAkOy
+xEqxvzdjtn3kqDM5Mjf0vyg0vu5ZRowAG5piKwIY+b+LfPz8YTfjXI/dbdCMe5J2
+u6V+XUnmrD0=
+-----END CERTIFICATE-----

ADD
assets/key.pem
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAor0bGOKA89Q1/ZdHLstg4J/5pNu5LuGn2qoae6AJR46HlbGx
+QHTy6h0au9+QcMF6muSOfT26uxAUkvsdeOHi6pu8lQeUKqkk0FGHZfdHznBSPAGq
+zT1wA5WjIdt0RERlSvchzyvSqKoJYIgXHTrG/RV9Z2ronY3ujtVh5JjsS1ddJSIB
+/McDg8ckpje91BR/t8RGD30qtG5tRMRTao/kBAhMlnrcGkzRxV4Q5vaFZLjmYkrs
+YKICmzdO+B+c8zuCPBysJWj22MXhIT+Cbb+QCUKUSflwKVkNtWbYYiVMw6JlWsq5
+dTjCjbK+Ly6lZTivV56uDJAwCoLaI7Jt/wDmTwIDAQABAoIBAQCEQB/kQjY/cKaL
+tkOf0KGjCf6rrfA5HCL3vaMV8kF4SGkBCs2cLy41eX9/TDiqmWFUvQba2q6EVJYT
+uEUENrXcqMFBb05GuPx02ryo7aMKLhd2MnimoBYGo7VVz20WCWsz19A+90MT1FMY
+gqAeHeUWST+HKsdtOqmyo9ARIQFuW34xs8tg9949bQoUQ8wKXEmv4XInILfG1sEl
+gFEazXnQtkz90PyzGiESqT8S8J/0sSFZrzF6HNAKv5FuY0lZXudmTtAlUGTIx+M/
+JuH2XBjAsGq6NC6Eptz9t4460XkUA9wQl7+liwqGW4sf8tAL8ardkOZ05zgQZkIy
+HbTmEybJAoGBANICI8qfJbdPq3G9MUyX1wuMvuU3FaoeqBs4br+qFPRJzLAchtK8
+JasrnZnSpvVWQqeUEYS9eLB9Ky8u6IjTOef3Mgjbx3sJjqoh+lG5ngwKr4iLo4d5
+JRcTI0M7D8l5hhOeZ0bv1ZB1HkV8yQQldxdEdr4DhxZ+glXbAL5m6B9dAoGBAMZg
+2bptJRirZQvythUb/sFFgVo/boVBJnMi4pVlK1vKcMPgV0N8M0+WnIvEN9yD3+4W
+addWuVB3SliVCPRtLnDLxbSutVIGIkCv8JxrrKjOHz7ktnPsvJmxirecL6+SPrwg
+DSzxUst+vvwCO1S5BpwLRi5ocMLf0xJDL6LC4/2bAoGBAKNM+KnpgPl/IRCrjjdB
+5v1bL2GrqNQFTLEF+9BcIDkpXdogBJK/rQbiPoXLcPpbXi9TCyBN+Rg77KWe80Da
+BzAiXyzWQdKhxubyzuRX0tcIRCCIfNuuTzIXNpdjyM3hCmodBa/6dPYErEpaUzE/
+NNDJ8w+kTJooO48pYfWsrZkdAoGAK9lAhxqU9oz7+tYdNTFI8EOCwgX/UekCrLRr
+TQZdvR5UKpet8jbDyLXLyIEIr+9eUewWXQjIUWbswgO/RPfVKg2Al91+KrE8ZPv+
+vTR3p6BX+7jmM12Cmp5JwSf+yloTD9Yt019MI7rSUDXWE3YKZfd//cYMFqcOqpy7
+LMjTSD0CgYB5sq4Pvdg66hz2fBnS3+l3b8FhaMprzA7thmCLzYh4ZFk+8xMv12ST
+UrqxYgmmFlluFNRE7M7P9Q2B0NaBC8BzwNLjwz06RWzI5JqZ34pzDw+vTmCgugN7
+qdjqZNt8KfNXTDtV9wsm6LfITkg/DWh0Uz9wD0JvTyQZpoB+qnjLtA==
+-----END RSA PRIVATE KEY-----

MODIFY
packages/@integration/src/effects/api.effects.ts
packages/@integration/src/effects/api.effects.ts
@@ -4,7 +4,7 @@ import { throwError } from 'rxjs';
import { map, mergeMap, tap } from 'rxjs/operators';
import { user$ } from './user.effects';
import { static$ } from './static.effects';
-import { WebSocketsToken } from '../tokens';
+import { WebSocketServerToken } from '../tokens';
const rootValiadtor$ = requestValidator$({
params: t.type({
@@ -22,7 +22,7 @@ const root$ = r.pipe(
use(rootValiadtor$),
map(req => req.params.version),
map(version => `API version: ${version}`),
- tap(message => inject(WebSocketsToken).sendBroadcastResponse({ type: 'ROOT', payload: message })),
+ tap(message => inject(WebSocketServerToken).sendBroadcastResponse({ type: 'ROOT', payload: message })),
map(message => ({ body: message })),
)));

MODIFY
packages/@integration/src/index.ts
packages/@integration/src/index.ts
@@ -4,14 +4,14 @@ import { merge } from 'rxjs';
import { tap, map } from 'rxjs/operators';
import { httpServer } from './http.listener';
import { webSocketServer } from './ws.listener';
-import { WebSocketsToken } from './tokens';
+import { WebSocketServerToken } from './tokens';
const upgrade$: HttpServerEffect = (event$, _, { inject }) =>
event$.pipe(
matchEvent(ServerEvent.upgrade),
mapToServer({
path: '/api/:version/ws',
- server: inject(WebSocketsToken),
+ server: inject(WebSocketServerToken),
}),
);
@@ -27,7 +27,7 @@ export const server = createServer({
port: 1337,
httpListener: httpServer,
dependencies: [
- bind(WebSocketsToken).to(webSocketServer({ noServer: true })),
+ bind(WebSocketServerToken).to(webSocketServer({ noServer: true })),
],
event$: (...args) => merge(
listen$(...args),

MODIFY
packages/@integration/src/tokens.ts
packages/@integration/src/tokens.ts
import { createInjectionToken } from '@marblejs/core';
import { MarbleWebSocketServer } from '@marblejs/websockets';
-export const WebSocketsToken = createInjectionToken<MarbleWebSocketServer>();
+export const WebSocketServerToken = createInjectionToken<MarbleWebSocketServer>();

MODIFY
packages/core/src/effects/http-effects.interface.ts
packages/core/src/effects/http-effects.interface.ts
import * as http from 'http';
+import * as https from 'https';
import { HttpRequest, HttpResponse, HttpStatus, HttpHeaders } from '../http.interface';
import { HttpError } from '../error/error.model';
import { Event } from '../event/event.interface';
@@ -19,7 +20,7 @@ export interface HttpErrorEffect<T extends Error = HttpError>
extends HttpEffect<HttpRequest, HttpEffectResponse, HttpResponse, T> {}
export interface HttpServerEffect<T extends Event = Event>
- extends HttpEffect<T, any, http.Server> {}
+ extends HttpEffect<T, any, http.Server | https.Server> {}
export interface HttpOutputEffect<T extends HttpEffectResponse = HttpEffectResponse>
extends HttpEffect<T, HttpEffectResponse> {}

MODIFY
packages/core/src/server/server.event.subscriber.ts
packages/core/src/server/server.event.subscriber.ts
import * as http from 'http';
+import * as https from 'https';
import * as net from 'net';
import { Subject } from 'rxjs';
import { ServerEventType, ServerEvent, AllServerEvents } from './server.event';
-export const subscribeServerEvents = (port?: number, hostname?: string) => (httpServer: http.Server) => {
+export const subscribeServerEvents = (port?: number, hostname?: string) => (httpServer: http.Server | https.Server) => {
const event$ = new Subject<AllServerEvents>();
httpServer.on(ServerEventType.CONNECT, () =>

MODIFY
packages/core/src/server/server.factory.ts
packages/core/src/server/server.factory.ts
import * as http from 'http';
+import * as https from 'https';
import { takeWhile } from 'rxjs/operators';
import { httpListener } from '../listener/http.listener';
import { isCloseEvent } from './server.event';
@@ -8,20 +9,27 @@ import { HttpServerEffect } from '../effects/http-effects.interface';
import { httpServerToken } from './server.token';
import { createEffectMetadata } from '../effects/effectsMetadata.factory';
+export interface ServerOptions {
+ httpsOptions?: https.ServerOptions;
+}
+
export interface CreateServerConfig {
port?: number;
hostname?: string;
httpListener: ReturnType<typeof httpListener>;
event$?: HttpServerEffect;
+ options?: ServerOptions;
dependencies?: InjectionDependencies;
}
export const createServer = (config: CreateServerConfig) => {
- const { httpListener, event$, port, hostname, dependencies } = config;
+ const { httpListener, event$, port, hostname, dependencies, options = {} } = config;
const { injector, routing } = httpListener.config;
const eventsSubscriber = subscribeServerEvents(port, hostname);
- const server = http.createServer(httpListener);
+ const server = options.httpsOptions
+ ? https.createServer(options.httpsOptions, httpListener)
+ : http.createServer(httpListener);
const serverEvent$ = eventsSubscriber(server).pipe(takeWhile(e => !isCloseEvent(e)));
injector.register(httpServerToken, () => server);

MODIFY
packages/core/src/server/specs/server.factory.spec.ts
packages/core/src/server/specs/server.factory.spec.ts
+import * as fs from 'fs';
+import * as path from 'path';
+import * as https from 'https';
import { forkJoin } from 'rxjs';
import { mapTo, tap, filter, take } from 'rxjs/operators';
import { httpListener } from '../../listener/http.listener';
@@ -67,6 +70,27 @@ describe('#createServer', () => {
expect(mocks.listen.mock.calls[0][1]).toBe(undefined);
});
+ test('creates https server', done => {
+ // given
+ const app = httpListener({ effects: [] });
+ const httpsOptions: https.ServerOptions = {
+ key: fs.readFileSync(path.resolve(__dirname, '../../../../../assets/key.pem')),
+ cert: fs.readFileSync(path.resolve(__dirname, '../../../../../assets/cert.pem')),
+ };
+
+ // when
+ marbleServer = createServer({
+ httpListener: app,
+ options: { httpsOptions },
+ });
+
+ // then
+ setTimeout(() => {
+ expect(marbleServer.server.listening).toBe(true);
+ marbleServer.server.close(done);
+ }, 100);
+ });
+
test('returns server and routing', () => {
// given
const effect$ = EffectFactory

feat(core): https servers support