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

feat(http-server): make http2 server creation extensible #2078

Closed
wants to merge 1 commit into from

Conversation

raymondfeng
Copy link
Contributor

@raymondfeng raymondfeng commented Nov 26, 2018

Supercedes #2065

Checklist

  • npm test passes on your machine
  • New tests added or existing tests modified to cover all changes
  • Code conforms with the style guide
  • API Documentation in code was updated
  • Documentation in /docs/site was updated
  • Affected artifact templates in packages/cli were updated
  • Affected example projects in examples/* were updated


/**
* HTTP / HTTPS server used by LoopBack's RestServer
*
* @export
* @class HttpServer
*/
export class HttpServer {
export class DefaultHttpServer implements HttpServer {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the impact of this change on possible users of @loopback/http-server?

For example, the following code creates a fully working HttpServer class in the current version (before your changes):

import * as turbo from 'turbo-http';

class TurboHttpServer extends HttpServer {
  constructor(
    requestListener: RequestListener,
    serverOptions?: HttpServerOptions,
  ) {
    super(requestListener, serverOptions);
    this.server = turbo.createServer(requestListener);
  }
}

IMO, we should preserve backwards compatibility in our API. Otherwise a new major version must be published, plus we will have to keep maintaining the old version according to our LTS policy...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can release @loopback/[email protected] if we have to break the compatibility due to the nature of this work.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can release @loopback/[email protected] if we have to break the compatibility due to the nature of this work.

Fair enough. In that case:

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See also #2103

* @interface Http2Options
*/
export interface Http2Options extends ListenerOptions {
protocol: 'http2';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The built-in http2 module support HTTP/2 protocol over plain unencrypted socket (similar to HTTP) or over TLS socket (similar to HTTPS). How do you envision encoding this configuration flag when there is a single protocol value http2?

await server.start();
expect(server.url).to.equal(`http://127.0.0.1:${server.port}`);
});

it('supports HTTP/2 protocol with key and certificate files', async () => {
// For some reason, spdy fails with Node 11
if (+process.versions.node.split('.')[0] > 10) return;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This early return will mark the test as passed, leading to false sense of security.

Please mark the test as skipped on Node.js 11.x. If we don't have a helper like "it.skipIf", then please consider creating one in @loopback/testlab.

it.skipIf(
  // For some reason, spdy fails with Node 11
  +process.versions.node.split('.')[0] > 10),
  'supports HTTP/2 protocol with key and certificate files',
  async () => {
    // ...
  }
);

if (+process.versions.node.split('.')[0] > 10) return;
const serverOptions: Http2Options = Object.assign(
{
protocol: 'http2' as 'http2',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'http2' as 'http2'

This looks weird, are you sure it's needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we don't cast 'http2' to 'http2', TS complains as it infers the type of http2 as string, which is not compatible with 'http' | 'https' | 'http2'.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Weird.

Can you add a comment to explain to readers please? (Similarly for 'h2' below.)

{
protocol: 'http2' as 'http2',
rejectUnauthorized: false,
spdy: {protocols: ['h2' as 'h2']},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

similarly here: 'h2' as 'h2'?

// We need to close the agent so that server.close() returns
// `@types/[email protected]` is not fully compatible with `[email protected]`
// tslint:disable-next-line:no-any
(agent as any).close();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please close the agent before making assertion.

In your current version, when expect(response.statusCode) throw an assertion error, then the agent is never closed.

packages/http-server/src/http-server.ts Outdated Show resolved Hide resolved
/**
* A factory to create http servers
*/
export interface HttpServerFactory {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use a plain function instead of an interface please?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted to make it possible to use dependency injection for the implementation.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. Can you enhance the TSDoc comment to explain why we choose an interface instead of a function?

agent?: https.Agent,
): Promise<IncomingMessage> {
agent =
agent ||
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find the formatting of this code block rather weird. Have you considered the following alternative?

const defaultAgent = new https.Agent({
 rejectUnauthorized: false,
});

export function httpsGetAsync(
   urlString: string,
   agent: https.Agent = defaultAgent,
): Promise<IncomingMessage> {
  // agent is always set now
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1.

@bajtos
Copy link
Member

bajtos commented Nov 30, 2018

@raymondfeng I am not very happy with the high-level design. Let's discuss and see if we can improve it.

I find unusual that we are injecting a factory. From what I've seen in applications using Dependency Injection, object creation (class instantiation) is usually handled by the IoC framework and the code receives the final object, not a factory to create it.

Can we change the design so that the HTTP Server implementation (e.g spdy) is configured by binding the server class, leaving the factory part for Context?

app.bind(RestBindings.Http.SERVER).toClass(Http2Server)

A typical HttpServer has two constructor-level dependencies: request-handler function and configuration object.

I think this change will greatly simplify both our implementation and also extensions providing custom servers.

@bajtos
Copy link
Member

bajtos commented Nov 30, 2018

One more reason why I don't like HttpServerFactory is that HttpServer itself is implementing as a factory that accepts protocol and creates a different underlying server instance based on the configured value (http.createServer, https.createServer, etc.)

}

export interface ProtocolServerFactory {
supports(protocol: string, serverOptions: HttpServerOptions): boolean;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting. The good part about this design is that it follows what we have already in place for body parsers. However, I feel it's unnecessarily complicated for the case of http servers.

For long term, I think we need to provide http2 support based on Node.js core module http2. From what I read, spdy does not work on Node.js 10+. Once this is done, most users should not need to change the way how HTTP servers are created at all.

@bajtos bajtos added feature REST Issues related to @loopback/rest package and REST transport in general labels Jan 7, 2019
@raeef-refai
Copy link

raeef-refai commented Jan 28, 2019

@raymondfeng @bajtos any news about this feature?

@stale
Copy link

stale bot commented Jan 24, 2020

This issue has been marked stale because it has not seen activity within six months. If you believe this to be in error, please contact one of the code owners, listed in the CODEOWNERS file at the top-level of this repository. This issue will be closed within 30 days of being stale.

@stale stale bot added the stale label Jan 24, 2020
@stale
Copy link

stale bot commented Feb 23, 2020

This issue has been closed due to continued inactivity. Thank you for your understanding. If you believe this to be in error, please contact one of the code owners, listed in the CODEOWNERS file at the top-level of this repository.

@stale stale bot closed this Feb 23, 2020
@vaibhavkumar-sf
Copy link
Contributor

Hey, we need http2 support in LB4,

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature REST Issues related to @loopback/rest package and REST transport in general stale
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants