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

Unify base path in HttpService #38237

Merged
merged 14 commits into from
Jun 16, 2019

Conversation

mshustov
Copy link
Contributor

@mshustov mshustov commented Jun 6, 2019

Summary

This PR contains breaking changes for NP plugins

Closes #35816
@eliperelman agreed on the proposed interface #36690 (comment)
as he is on vacation I implemented for both client and server.

What's done:

  • client and server use common modifyUrl helpers.
  • on client and server provide unified interfaces
// client
interface BasePath {
    get: () => string;
    prepend: (url: string) => string;
    remove: (url: string) => string;
};
// server
interface BasePath {
    // probably we will make request optional to return default basePath from config
    get: (request: KibanaRequest | Request) => string;
    set: (request: KibanaRequest | Request, basePath: string) => void;
    prepend: (url: string) => string;
    remove: (url: string) => string;
};

Checklist

Use strikethroughs to remove checklist items you don't feel are applicable to this PR.

For maintainers

Dev Docs

All requests to Kibana server should be concerned about server.basePath option, which affects all routes when Kibana is run behind a proxy. To simplify URL manipulations to include/exclude server.basePath part, New platform collocates under basePath namespace a set of helpers as a part of CoreSetup.http contract:

// on client side
basePath: {
  get: () => string;
  prepend: (url: string) => string;
  remove: (url: string) => string;
};
// on server side
basePath {
  get: (request: KibanaRequest | Request) => string;
  set: (request: KibanaRequest | Request, basePath: string) => void;
  prepend: (url: string) => string;
  remove: (url: string) => string;
};

@mshustov mshustov added release_note:breaking Team:Core Core services & architecture: plugins, logging, config, saved objects, http, ES client, i18n, etc Feature:New Platform release_note:plugin_api_changes Contains a Plugin API changes section for the breaking plugin API changes section. v7.3.0 labels Jun 6, 2019
@elasticmachine
Copy link
Contributor

Pinging @elastic/kibana-platform

slashes: boolean | null;
port: string | null;
query: ParsedUrlQuery | {};
auth?: string | null;
Copy link
Contributor Author

@mshustov mshustov Jun 6, 2019

Choose a reason for hiding this comment

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

@azasypkin what do you think if we relay on node typings and get rid of null?

import { UrlWithParsedQuery } from 'url';
export type URLMeaningfulParts = Pick<
  UrlWithParsedQuery,
  'auth' | 'hash' | 'hostname' | 'pathname' | 'protocol' | 'slashes' | 'port' | 'query'
>;

Copy link
Member

Choose a reason for hiding this comment

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

I'm fine if Node typings are correct. IIRC last time I checked these didn't reflect completely what was happening in reality, like Node typings were saying that auth/whatever?: string, but in reality it was auth: null | string.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Indeed, it works as you described and that's very confusing. Let's keep it as is. I will add a comment to re-consider behavior with newer @types/node version, because the latest v10.12.30 has the same problems.

Copy link
Contributor

Choose a reason for hiding this comment

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

We should consider submitting an upstream PR, in my experience it's pretty quick to get fixes merged into the types project.

public prepend = (path: string): string => {
if (!this.basePath) return path;
return modifyUrl(path, parts => {
if (!parts.hostname && parts.pathname && parts.pathname.startsWith('/')) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@mshustov mshustov added the review label Jun 6, 2019
@mshustov mshustov marked this pull request as ready for review June 6, 2019 07:54
@mshustov mshustov requested a review from a team as a code owner June 6, 2019 07:54
@mshustov
Copy link
Contributor Author

mshustov commented Jun 6, 2019

@joshdover should we attach release_note:breaking label for experimental API?

@elasticmachine
Copy link
Contributor

💔 Build Failed

basePath: {
get: (request: KibanaRequest | Request) => string;
set: (request: KibanaRequest | Request, basePath: string) => void;
prepend: (url: string) => string;
Copy link
Contributor Author

@mshustov mshustov Jun 6, 2019

Choose a reason for hiding this comment

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

prepend on client and server are different a bit. basePath can be request specific on the server side, so probably we can extend api to

prepend: (url: string) => string;
prepend: (request: KibanaRequest | Request,  url: string) => string;

in our codebase https://github.com/restrry/kibana/blob/33c08f4e20b92e36c8a0df37528f410ad808d73c/x-pack/plugins/security/server/lib/authentication/providers/basic.ts#L93

Copy link
Contributor

Choose a reason for hiding this comment

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

The request-specific basePath is needed because of spaces, correct? The client still needs the space prefix in its basePath, but the user can only be on a single space at a given time, where as the server may be serving multiple spaces at a time. For this reason, the request needs to be passed to the basePath methods on the server each time we get the basePath, but on the client, the current space is globally configured so this is not needed.

Just trying to make sure I understand.

Copy link
Contributor Author

@mshustov mshustov Jun 7, 2019

Choose a reason for hiding this comment

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

with handler RFC we might want to unify them, but atm it works as you described.

Copy link
Contributor

Choose a reason for hiding this comment

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

Another way to make prepend and other helpers request specific would be to make these static functions on the BasePath class:

prepend: (basepath: Basepath, url) => string;

This way these helpers are exactly the same between client and server and there's only one type signature.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

in this case we have to create BastPath instance for every Request. don't we?

Copy link
Contributor

Choose a reason for hiding this comment

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

yeah it gets a bit weird... BasePath as a data type isn't that useful since it's really just a string, so we could just accept a string instead:

prepend: (basepath: string, url) => string;

Copy link
Contributor Author

Choose a reason for hiding this comment

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

that's the thing, end-user doesn't know basepath value, it is stored in the core by Spaces plugin

src/core/server/http/http_server.ts Outdated Show resolved Hide resolved
src/core/server/http/base_path_service.ts Outdated Show resolved Hide resolved
basePath: {
get: (request: KibanaRequest | Request) => string;
set: (request: KibanaRequest | Request, basePath: string) => void;
prepend: (url: string) => string;
Copy link
Contributor

Choose a reason for hiding this comment

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

The request-specific basePath is needed because of spaces, correct? The client still needs the space prefix in its basePath, but the user can only be on a single space at a given time, where as the server may be serving multiple spaces at a time. For this reason, the request needs to be passed to the basePath methods on the server each time we get the basePath, but on the client, the current space is globally configured so this is not needed.

Just trying to make sure I understand.

@mshustov mshustov requested a review from joshdover June 7, 2019 14:22
@elasticmachine
Copy link
Contributor

💚 Build Succeeded

@elastic elastic deleted a comment from elasticmachine Jun 7, 2019
@elasticmachine
Copy link
Contributor

💔 Build Failed

@mshustov
Copy link
Contributor Author

retest

basePath: {
get: (request: KibanaRequest | Request) => string;
set: (request: KibanaRequest | Request, basePath: string) => void;
prepend: (url: string) => string;
Copy link
Contributor

Choose a reason for hiding this comment

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

Another way to make prepend and other helpers request specific would be to make these static functions on the BasePath class:

prepend: (basepath: Basepath, url) => string;

This way these helpers are exactly the same between client and server and there's only one type signature.

expect(basePath.prepend('/a/b?x=y#c/d/e')).toBe('/foo/bar/a/b?x=y#c/d/e');
});

it('returns the path unchanged if it does not start with a slash', () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Optional: This is the same as the existing behaviour, but I'm not sure why we implemented this behaviour. It feels like your code can silently fail if you forget a / in a path you're prepending. The only way you would notice this mistake is by testing it and noticing that the basepath was never added which feels brittle.

Is there a reason we can't handle this case by inserting a /? If there is a good reason we don't want to accept strings not starting with / should we perhaps throw an exception to show that this is unexpected/unsupported behaviour instead of pretending like we're giving you a prepended basepath when we are not.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ping @spalger as an author

Copy link
Contributor

Choose a reason for hiding this comment

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

I agree that the implicitness is undesirable. I originally did it this way so that the code would ignore fully resolved urls, but I'm not sure if that's being used... sounds like a really tricky thing to figure out.

slashes: boolean | null;
port: string | null;
query: ParsedUrlQuery | {};
auth?: string | null;
Copy link
Contributor

Choose a reason for hiding this comment

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

We should consider submitting an upstream PR, in my experience it's pretty quick to get fixes merged into the types project.

@elasticmachine
Copy link
Contributor

💚 Build Succeeded

@joshdover
Copy link
Contributor

@joshdover should we attach release_note:breaking label for experimental API?

Got the answer from the docs team: only use release_note:dev_docs for breaking changes to experimental APIs.

@mshustov mshustov requested review from rudolf and a team June 14, 2019 11:59
@mshustov mshustov removed the review label Jun 16, 2019
@elasticmachine
Copy link
Contributor

💚 Build Succeeded

@mshustov mshustov merged commit de5c452 into elastic:master Jun 16, 2019
@mshustov mshustov deleted the issue-35816-unify-base-path branch June 16, 2019 14:36
mshustov added a commit to mshustov/kibana that referenced this pull request Jun 16, 2019
* unify modifyUrl on client and server

* create BasePath as a separate entity on server

* use BasePath class in http server

* use BasePath a separate entity on client

* use BasePath class on Http service on the client

* switch client code to the new api

* improve setver http service mocks

* address comments #1

* address comments #2

* update docs

* add comment why we define own typings
mshustov added a commit that referenced this pull request Jun 16, 2019
* unify modifyUrl on client and server

* create BasePath as a separate entity on server

* use BasePath class in http server

* use BasePath a separate entity on client

* use BasePath class on Http service on the client

* switch client code to the new api

* improve setver http service mocks

* address comments #1

* address comments #2

* update docs

* add comment why we define own typings
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Feature:New Platform release_note:plugin_api_changes Contains a Plugin API changes section for the breaking plugin API changes section. Team:Core Core services & architecture: plugins, logging, config, saved objects, http, ES client, i18n, etc v7.3.0
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Unify BasePath service on client and server
6 participants