Unify base path in HttpService (#38237)

* 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
This commit is contained in:
Mikhail Shustov 2019-06-16 16:36:02 +02:00 committed by GitHub
parent 9c1bdb9298
commit de5c452f14
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
38 changed files with 521 additions and 525 deletions

View file

@ -0,0 +1,15 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-public](./kibana-plugin-public.md) &gt; [HttpServiceBase](./kibana-plugin-public.httpservicebase.md) &gt; [basePath](./kibana-plugin-public.httpservicebase.basepath.md)
## HttpServiceBase.basePath property
<b>Signature:</b>
```typescript
basePath: {
get: () => string;
prepend: (url: string) => string;
remove: (url: string) => string;
};
```

View file

@ -1,15 +0,0 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-public](./kibana-plugin-public.md) &gt; [HttpServiceBase](./kibana-plugin-public.httpservicebase.md) &gt; [getBasePath](./kibana-plugin-public.httpservicebase.getbasepath.md)
## HttpServiceBase.getBasePath() method
<b>Signature:</b>
```typescript
getBasePath(): string;
```
<b>Returns:</b>
`string`

View file

@ -15,6 +15,7 @@ export interface HttpServiceBase
| Property | Type | Description |
| --- | --- | --- |
| [basePath](./kibana-plugin-public.httpservicebase.basepath.md) | <code>{</code><br/><code> get: () =&gt; string;</code><br/><code> prepend: (url: string) =&gt; string;</code><br/><code> remove: (url: string) =&gt; string;</code><br/><code> }</code> | |
| [delete](./kibana-plugin-public.httpservicebase.delete.md) | <code>HttpHandler</code> | |
| [fetch](./kibana-plugin-public.httpservicebase.fetch.md) | <code>HttpHandler</code> | |
| [get](./kibana-plugin-public.httpservicebase.get.md) | <code>HttpHandler</code> | |
@ -29,11 +30,8 @@ export interface HttpServiceBase
| Method | Description |
| --- | --- |
| [addLoadingCount(count$)](./kibana-plugin-public.httpservicebase.addloadingcount.md) | |
| [getBasePath()](./kibana-plugin-public.httpservicebase.getbasepath.md) | |
| [getLoadingCount$()](./kibana-plugin-public.httpservicebase.getloadingcount$.md) | |
| [intercept(interceptor)](./kibana-plugin-public.httpservicebase.intercept.md) | |
| [prependBasePath(path)](./kibana-plugin-public.httpservicebase.prependbasepath.md) | |
| [removeAllInterceptors()](./kibana-plugin-public.httpservicebase.removeallinterceptors.md) | |
| [removeBasePath(path)](./kibana-plugin-public.httpservicebase.removebasepath.md) | |
| [stop()](./kibana-plugin-public.httpservicebase.stop.md) | |

View file

@ -1,22 +0,0 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-public](./kibana-plugin-public.md) &gt; [HttpServiceBase](./kibana-plugin-public.httpservicebase.md) &gt; [prependBasePath](./kibana-plugin-public.httpservicebase.prependbasepath.md)
## HttpServiceBase.prependBasePath() method
<b>Signature:</b>
```typescript
prependBasePath(path: string): string;
```
## Parameters
| Parameter | Type | Description |
| --- | --- | --- |
| path | <code>string</code> | |
<b>Returns:</b>
`string`

View file

@ -1,22 +0,0 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-public](./kibana-plugin-public.md) &gt; [HttpServiceBase](./kibana-plugin-public.httpservicebase.md) &gt; [removeBasePath](./kibana-plugin-public.httpservicebase.removebasepath.md)
## HttpServiceBase.removeBasePath() method
<b>Signature:</b>
```typescript
removeBasePath(path: string): string;
```
## Parameters
| Parameter | Type | Description |
| --- | --- | --- |
| path | <code>string</code> | |
<b>Returns:</b>
`string`

View file

@ -11,8 +11,7 @@ http: {
registerOnPreAuth: HttpServiceSetup['registerOnPreAuth'];
registerAuth: HttpServiceSetup['registerAuth'];
registerOnPostAuth: HttpServiceSetup['registerOnPostAuth'];
getBasePathFor: HttpServiceSetup['getBasePathFor'];
setBasePathFor: HttpServiceSetup['setBasePathFor'];
basePath: HttpServiceSetup['basePath'];
createNewServer: HttpServiceSetup['createNewServer'];
};
```

View file

@ -17,5 +17,5 @@ export interface CoreSetup
| Property | Type | Description |
| --- | --- | --- |
| [elasticsearch](./kibana-plugin-server.coresetup.elasticsearch.md) | <code>{</code><br/><code> adminClient$: Observable&lt;ClusterClient&gt;;</code><br/><code> dataClient$: Observable&lt;ClusterClient&gt;;</code><br/><code> }</code> | |
| [http](./kibana-plugin-server.coresetup.http.md) | <code>{</code><br/><code> registerOnPreAuth: HttpServiceSetup['registerOnPreAuth'];</code><br/><code> registerAuth: HttpServiceSetup['registerAuth'];</code><br/><code> registerOnPostAuth: HttpServiceSetup['registerOnPostAuth'];</code><br/><code> getBasePathFor: HttpServiceSetup['getBasePathFor'];</code><br/><code> setBasePathFor: HttpServiceSetup['setBasePathFor'];</code><br/><code> createNewServer: HttpServiceSetup['createNewServer'];</code><br/><code> }</code> | |
| [http](./kibana-plugin-server.coresetup.http.md) | <code>{</code><br/><code> registerOnPreAuth: HttpServiceSetup['registerOnPreAuth'];</code><br/><code> registerAuth: HttpServiceSetup['registerAuth'];</code><br/><code> registerOnPostAuth: HttpServiceSetup['registerOnPostAuth'];</code><br/><code> basePath: HttpServiceSetup['basePath'];</code><br/><code> createNewServer: HttpServiceSetup['createNewServer'];</code><br/><code> }</code> | |

View file

@ -29,7 +29,9 @@ const mockAppService = {
} as any;
const mockHttp = {
prependBasePath: (url: string) => `wow${url}`,
basePath: {
prepend: (url: string) => `wow${url}`,
},
} as any;
describe('NavLinksService', () => {

View file

@ -42,7 +42,7 @@ export class NavLinksService {
new NavLinkWrapper({
...app,
// Either rootRoute or appUrl must be defined.
baseUrl: relativeToAbsolute(http.prependBasePath((app.rootRoute || app.appUrl)!)),
baseUrl: relativeToAbsolute(http.basePath.prepend((app.rootRoute || app.appUrl)!)),
}),
] as [string, NavLinkWrapper]
)

View file

@ -0,0 +1,91 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { BasePath } from './base_path_service';
describe('BasePath', () => {
describe('#get()', () => {
it('returns an empty string if no basePath not provided', () => {
expect(new BasePath().get()).toBe('');
});
it('returns basePath value if provided', () => {
expect(new BasePath('/foo').get()).toBe('/foo');
});
describe('#prepend()', () => {
it('adds the base path to the path if it is relative and starts with a slash', () => {
const basePath = new BasePath('/foo/bar');
expect(basePath.prepend('/a/b')).toBe('/foo/bar/a/b');
});
it('leaves the query string and hash of path unchanged', () => {
const basePath = new BasePath('/foo/bar');
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', () => {
const basePath = new BasePath('/foo/bar');
expect(basePath.prepend('a/b')).toBe('a/b');
});
it('returns the path unchanged it it has a hostname', () => {
const basePath = new BasePath('/foo/bar');
expect(basePath.prepend('http://localhost:5601/a/b')).toBe('http://localhost:5601/a/b');
});
});
describe('#remove()', () => {
it('removes the basePath if relative path starts with it', () => {
const basePath = new BasePath('/foo/bar');
expect(basePath.remove('/foo/bar/a/b')).toBe('/a/b');
});
it('leaves query string and hash intact', () => {
const basePath = new BasePath('/foo/bar');
expect(basePath.remove('/foo/bar/a/b?c=y#1234')).toBe('/a/b?c=y#1234');
});
it('ignores urls with hostnames', () => {
const basePath = new BasePath('/foo/bar');
expect(basePath.remove('http://localhost:5601/foo/bar/a/b')).toBe(
'http://localhost:5601/foo/bar/a/b'
);
});
it('returns slash if path is just basePath', () => {
const basePath = new BasePath('/foo/bar');
expect(basePath.remove('/foo/bar')).toBe('/');
});
it('returns full path if basePath is not its own segment', () => {
const basePath = new BasePath('/foo/bar');
expect(basePath.remove('/foo/barhop')).toBe('/foo/barhop');
});
});
});
});

View file

@ -0,0 +1,71 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { modifyUrl } from '../../utils';
export class BasePath {
constructor(private readonly basePath: string = '') {}
public get = () => {
return this.basePath;
};
public prepend = (path: string): string => {
if (!this.basePath) return path;
return modifyUrl(path, parts => {
if (!parts.hostname && parts.pathname && parts.pathname.startsWith('/')) {
parts.pathname = `${this.basePath}${parts.pathname}`;
}
});
};
public remove = (path: string): string => {
if (!this.basePath) {
return path;
}
if (path === this.basePath) {
return '/';
}
if (path.startsWith(`${this.basePath}/`)) {
return path.slice(this.basePath.length);
}
return path;
};
}

View file

@ -18,9 +18,13 @@
*/
import { HttpService } from './http_service';
import { HttpSetup, HttpStart } from './types';
import { HttpSetup } from './types';
const createServiceMock = () => ({
type ServiceSetupMockType = jest.Mocked<HttpSetup> & {
basePath: jest.Mocked<HttpSetup['basePath']>;
};
const createServiceMock = (): ServiceSetupMockType => ({
fetch: jest.fn(),
get: jest.fn(),
head: jest.fn(),
@ -29,9 +33,11 @@ const createServiceMock = () => ({
patch: jest.fn(),
delete: jest.fn(),
options: jest.fn(),
getBasePath: jest.fn(),
prependBasePath: jest.fn(),
removeBasePath: jest.fn(),
basePath: {
get: jest.fn(),
prepend: jest.fn(),
remove: jest.fn(),
},
addLoadingCount: jest.fn(),
getLoadingCount$: jest.fn(),
stop: jest.fn(),
@ -39,13 +45,19 @@ const createServiceMock = () => ({
removeAllInterceptors: jest.fn(),
});
const createSetupContractMock = (): jest.Mocked<HttpSetup> => createServiceMock();
const createStartContractMock = (): jest.Mocked<HttpStart> => createServiceMock();
const createMock = (): jest.Mocked<PublicMethodsOf<HttpService>> => ({
setup: jest.fn().mockReturnValue(createSetupContractMock()),
start: jest.fn().mockReturnValue(createStartContractMock()),
stop: jest.fn(),
});
const createSetupContractMock = () => createServiceMock();
const createStartContractMock = () => createServiceMock();
const createMock = () => {
const mocked: jest.Mocked<Required<HttpService>> = {
setup: jest.fn(),
start: jest.fn(),
stop: jest.fn(),
};
mocked.setup.mockReturnValue(createSetupContractMock());
mocked.start.mockReturnValue(createSetupContractMock());
return mocked;
};
export const httpServiceMock = {
create: createMock,

View file

@ -29,79 +29,19 @@ const setupFakeBasePath: SetupTap = injectedMetadata => {
injectedMetadata.getBasePath.mockReturnValue('/foo/bar');
};
describe('getBasePath', () => {
describe('basePath.get()', () => {
it('returns an empty string if no basePath is injected', () => {
const { http } = setup(injectedMetadata => {
injectedMetadata.getBasePath.mockReturnValue('');
injectedMetadata.getBasePath.mockReturnValue(undefined as any);
});
expect(http.getBasePath()).toBe('');
expect(http.basePath.get()).toBe('');
});
it('returns the injected basePath', () => {
const { http } = setup(setupFakeBasePath);
expect(http.getBasePath()).toBe('/foo/bar');
});
});
describe('prependBasePath', () => {
it('adds the base path to the path if it is relative and starts with a slash', () => {
const { http } = setup(setupFakeBasePath);
expect(http.prependBasePath('/a/b')).toBe('/foo/bar/a/b');
});
it('leaves the query string and hash of path unchanged', () => {
const { http } = setup(setupFakeBasePath);
expect(http.prependBasePath('/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', () => {
const { http } = setup(setupFakeBasePath);
expect(http.prependBasePath('a/b')).toBe('a/b');
});
it('returns the path unchanged it it has a hostname', () => {
const { http } = setup(setupFakeBasePath);
expect(http.prependBasePath('http://localhost:5601/a/b')).toBe('http://localhost:5601/a/b');
});
});
describe('removeBasePath', () => {
it('removes the basePath if relative path starts with it', () => {
const { http } = setup(setupFakeBasePath);
expect(http.removeBasePath('/foo/bar/a/b')).toBe('/a/b');
});
it('leaves query string and hash intact', () => {
const { http } = setup(setupFakeBasePath);
expect(http.removeBasePath('/foo/bar/a/b?c=y#1234')).toBe('/a/b?c=y#1234');
});
it('ignores urls with hostnames', () => {
const { http } = setup(setupFakeBasePath);
expect(http.removeBasePath('http://localhost:5601/foo/bar/a/b')).toBe(
'http://localhost:5601/foo/bar/a/b'
);
});
it('returns slash if path is just basePath', () => {
const { http } = setup(setupFakeBasePath);
expect(http.removeBasePath('/foo/bar')).toBe('/');
});
it('returns full path if basePath is not its own segment', () => {
const { http } = setup(setupFakeBasePath);
expect(http.removeBasePath('/foo/barhop')).toBe('/foo/barhop');
expect(http.basePath.get()).toBe('/foo/bar');
});
});

View file

@ -31,11 +31,11 @@ import { merge } from 'lodash';
import { format } from 'url';
import { InjectedMetadataSetup } from '../injected_metadata';
import { FatalErrorsSetup } from '../fatal_errors';
import { modifyUrl } from '../utils';
import { HttpFetchOptions, HttpServiceBase, HttpInterceptor, HttpResponse } from './types';
import { HttpInterceptController } from './http_intercept_controller';
import { HttpFetchError } from './http_fetch_error';
import { HttpInterceptHaltError } from './http_intercept_halt_error';
import { BasePath } from './base_path_service';
const JSON_CONTENT = /^(application\/(json|x-javascript)|text\/(x-)?javascript|x-json)(;.*)?$/;
const NDJSON_CONTENT = /^(application\/ndjson)(;.*)?$/;
@ -48,7 +48,7 @@ export const setup = (
const stop$ = new Subject();
const interceptors = new Set<HttpInterceptor>();
const kibanaVersion = injectedMetadata.getKibanaVersion();
const basePath = injectedMetadata.getBasePath() || '';
const basePath = new BasePath(injectedMetadata.getBasePath());
function intercept(interceptor: HttpInterceptor) {
interceptors.add(interceptor);
@ -60,14 +60,6 @@ export const setup = (
interceptors.clear();
}
function prependBasePath(path: string): string {
return modifyUrl(path, parts => {
if (!parts.hostname && parts.pathname && parts.pathname.startsWith('/')) {
parts.pathname = `${basePath}${parts.pathname}`;
}
});
}
function createRequest(path: string, options?: HttpFetchOptions) {
const { query, prependBasePath: shouldPrependBasePath, ...fetchOptions } = merge(
{
@ -82,7 +74,7 @@ export const setup = (
options || {}
);
const url = format({
pathname: shouldPrependBasePath ? prependBasePath(path) : path,
pathname: shouldPrependBasePath ? basePath.prepend(path) : path,
query,
});
@ -255,26 +247,6 @@ export const setup = (
loadingCount$.complete();
}
function getBasePath() {
return basePath;
}
function removeBasePath(path: string): string {
if (!basePath) {
return path;
}
if (path === basePath) {
return '/';
}
if (path.startsWith(`${basePath}/`)) {
return path.slice(basePath.length);
}
return path;
}
function addLoadingCount(count$: Observable<number>) {
count$
.pipe(
@ -314,9 +286,7 @@ export const setup = (
return {
stop,
getBasePath,
prependBasePath,
removeBasePath,
basePath,
intercept,
removeAllInterceptors,
fetch,

View file

@ -26,9 +26,11 @@ import { HttpFetchError } from './http_fetch_error';
/** @public */
export interface HttpServiceBase {
stop(): void;
getBasePath(): string;
prependBasePath(path: string): string;
removeBasePath(path: string): string;
basePath: {
get: () => string;
prepend: (url: string) => string;
remove: (url: string) => string;
};
intercept(interceptor: HttpInterceptor): () => void;
removeAllInterceptors(): void;
fetch: HttpHandler;

View file

@ -167,9 +167,9 @@ test('`PluginsService.setup` calls loadPluginBundles with http and plugins', asy
await pluginsService.setup(mockSetupDeps);
expect(mockLoadPluginBundle).toHaveBeenCalledTimes(3);
expect(mockLoadPluginBundle).toHaveBeenCalledWith(mockSetupDeps.http.prependBasePath, 'pluginA');
expect(mockLoadPluginBundle).toHaveBeenCalledWith(mockSetupDeps.http.prependBasePath, 'pluginB');
expect(mockLoadPluginBundle).toHaveBeenCalledWith(mockSetupDeps.http.prependBasePath, 'pluginC');
expect(mockLoadPluginBundle).toHaveBeenCalledWith(mockSetupDeps.http.basePath.prepend, 'pluginA');
expect(mockLoadPluginBundle).toHaveBeenCalledWith(mockSetupDeps.http.basePath.prepend, 'pluginB');
expect(mockLoadPluginBundle).toHaveBeenCalledWith(mockSetupDeps.http.basePath.prepend, 'pluginC');
});
test('`PluginsService.setup` initalizes plugins with CoreContext', async () => {

View file

@ -70,7 +70,7 @@ export class PluginsService implements CoreService<PluginsServiceSetup, PluginsS
);
// Load plugin bundles
await this.loadPluginBundles(deps.http.prependBasePath);
await this.loadPluginBundles(deps.http.basePath.prepend);
// Setup each plugin with required and optional plugin contracts
const contracts = new Map<string, unknown>();

View file

@ -190,6 +190,12 @@ export interface HttpServiceBase {
// (undocumented)
addLoadingCount(count$: Observable<number>): void;
// (undocumented)
basePath: {
get: () => string;
prepend: (url: string) => string;
remove: (url: string) => string;
};
// (undocumented)
delete: HttpHandler;
// Warning: (ae-forgotten-export) The symbol "HttpHandler" needs to be exported by the entry point index.d.ts
//
@ -198,8 +204,6 @@ export interface HttpServiceBase {
// (undocumented)
get: HttpHandler;
// (undocumented)
getBasePath(): string;
// (undocumented)
getLoadingCount$(): Observable<number>;
// (undocumented)
head: HttpHandler;
@ -212,14 +216,10 @@ export interface HttpServiceBase {
// (undocumented)
post: HttpHandler;
// (undocumented)
prependBasePath(path: string): string;
// (undocumented)
put: HttpHandler;
// (undocumented)
removeAllInterceptors(): void;
// (undocumented)
removeBasePath(path: string): string;
// (undocumented)
stop(): void;
}

View file

@ -20,20 +20,22 @@ exports[`#setup constructs UiSettingsClient and UiSettingsApi: UiSettingsApi arg
},
],
},
"basePath": Object {
"get": [MockFunction],
"prepend": [MockFunction],
"remove": [MockFunction],
},
"delete": [MockFunction],
"fetch": [MockFunction],
"get": [MockFunction],
"getBasePath": [MockFunction],
"getLoadingCount$": [MockFunction],
"head": [MockFunction],
"intercept": [MockFunction],
"options": [MockFunction],
"patch": [MockFunction],
"post": [MockFunction],
"prependBasePath": [MockFunction],
"put": [MockFunction],
"removeAllInterceptors": [MockFunction],
"removeBasePath": [MockFunction],
"stop": [MockFunction],
},
],

View file

@ -17,5 +17,4 @@
* under the License.
*/
export { modifyUrl } from './modify_url';
export { shareWeakReplay } from './share_weak_replay';

View file

@ -1,59 +0,0 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { modifyUrl } from './modify_url';
it('supports returning a new url spec', () => {
expect(modifyUrl('http://localhost', () => ({}))).toBe('');
});
it('supports modifying the passed object', () => {
expect(
modifyUrl('http://localhost', parsed => {
parsed.port = '9999';
parsed.auth = 'foo:bar';
})
).toBe('http://foo:bar@localhost:9999/');
});
it('supports changing pathname', () => {
expect(
modifyUrl('http://localhost/some/path', parsed => {
parsed.pathname += '/subpath';
})
).toBe('http://localhost/some/path/subpath');
});
it('supports changing port', () => {
expect(
modifyUrl('http://localhost:5601', parsed => {
parsed.port = String(Number(parsed.port) + 1);
})
).toBe('http://localhost:5602/');
});
it('supports changing protocol', () => {
expect(
modifyUrl('http://localhost', parsed => {
parsed.protocol = 'mail';
parsed.slashes = false;
parsed.pathname = undefined;
})
).toBe('mail:localhost');
});

View file

@ -1,95 +0,0 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { format as formatUrl, parse as parseUrl } from 'url';
interface UrlParts {
protocol?: string;
slashes?: boolean;
auth?: string;
hostname?: string;
port?: string;
pathname?: string;
query: { [key: string]: string | string[] | undefined };
hash?: string;
}
/**
* Takes a URL and a function that takes the meaningful parts
* of the URL as a key-value object, modifies some or all of
* the parts, and returns the modified parts formatted again
* as a url.
*
* Url Parts sent:
* - protocol
* - slashes (does the url have the //)
* - auth
* - hostname (just the name of the host, no port or auth information)
* - port
* - pathname (the path after the hostname, no query or hash, starts
* with a slash if there was a path)
* - query (always an object, even when no query on original url)
* - hash
*
* Why?
* - The default url library in node produces several conflicting
* properties on the "parsed" output. Modifying any of these might
* lead to the modifications being ignored (depending on which
* property was modified)
* - It's not always clear wither to use path/pathname, host/hostname,
* so this tries to add helpful constraints
*
* @param url the url to parse
* @param block a function that will modify the parsed url, or return a new one
*/
export function modifyUrl(url: string, block: (parts: UrlParts) => Partial<UrlParts> | void) {
const parsed = parseUrl(url, true);
// copy over the most specific version of each
// property. By default, the parsed url includes
// several conflicting properties (like path and
// pathname + search, or search and query) and keeping
// track of which property is actually used when they
// are formatted is harder than necessary
const meaningfulParts = {
protocol: parsed.protocol,
slashes: parsed.slashes,
auth: parsed.auth,
hostname: parsed.hostname,
port: parsed.port,
pathname: parsed.pathname,
query: parsed.query || {},
hash: parsed.hash,
};
// the block modifies the meaningfulParts object, or returns a new one
const modifiedParts = block(meaningfulParts) || meaningfulParts;
// format the modified/replaced meaningfulParts back into a url
return formatUrl({
protocol: modifiedParts.protocol,
slashes: modifiedParts.slashes,
auth: modifiedParts.auth,
hostname: modifiedParts.hostname,
port: modifiedParts.port,
pathname: modifiedParts.pathname,
query: modifiedParts.query,
hash: modifiedParts.hash,
});
}

View file

@ -0,0 +1,132 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { BasePath } from './base_path_service';
import { KibanaRequest } from './router';
import { httpServerMock } from './http_server.mocks';
describe('BasePath', () => {
describe('#get()', () => {
it('returns base path associated with an incoming Legacy.Request request', () => {
const request = httpServerMock.createRawRequest();
const basePath = new BasePath();
basePath.set(request, '/baz/');
expect(basePath.get(request)).toBe('/baz/');
});
it('returns base path associated with an incoming KibanaRequest', () => {
const request = httpServerMock.createRawRequest();
const basePath = new BasePath();
basePath.set(KibanaRequest.from(request, undefined), '/baz/');
expect(basePath.get(KibanaRequest.from(request, undefined))).toBe('/baz/');
});
it('operates with both Legacy.Request/KibanaRequest requests', () => {
const request = httpServerMock.createRawRequest();
const basePath = new BasePath();
basePath.set(request, '/baz/');
expect(basePath.get(KibanaRequest.from(request, undefined))).toBe('/baz/');
});
it('is based on server base path', () => {
const request = httpServerMock.createRawRequest();
const basePath = new BasePath('/foo/bar');
basePath.set(request, '/baz/');
expect(basePath.get(request)).toBe('/foo/bar/baz/');
});
});
describe('#set()', () => {
it('#set() cannot be set twice for one request', () => {
const request = httpServerMock.createRawRequest();
const basePath = new BasePath('/foo/bar');
const setPath = () => basePath.set(request, 'baz/');
setPath();
expect(setPath).toThrowErrorMatchingInlineSnapshot(
`"Request basePath was previously set. Setting multiple times is not supported."`
);
});
});
describe('#prepend()', () => {
it('adds the base path to the path if it is relative and starts with a slash', () => {
const basePath = new BasePath('/foo/bar');
expect(basePath.prepend('/a/b')).toBe('/foo/bar/a/b');
});
it('leaves the query string and hash of path unchanged', () => {
const basePath = new BasePath('/foo/bar');
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', () => {
const basePath = new BasePath('/foo/bar');
expect(basePath.prepend('a/b')).toBe('a/b');
});
it('returns the path unchanged it it has a hostname', () => {
const basePath = new BasePath('/foo/bar');
expect(basePath.prepend('http://localhost:5601/a/b')).toBe('http://localhost:5601/a/b');
});
});
describe('#remove()', () => {
it('removes the basePath if relative path starts with it', () => {
const basePath = new BasePath('/foo/bar');
expect(basePath.remove('/foo/bar/a/b')).toBe('/a/b');
});
it('leaves query string and hash intact', () => {
const basePath = new BasePath('/foo/bar');
expect(basePath.remove('/foo/bar/a/b?c=y#1234')).toBe('/a/b?c=y#1234');
});
it('ignores urls with hostnames', () => {
const basePath = new BasePath('/foo/bar');
expect(basePath.remove('http://localhost:5601/foo/bar/a/b')).toBe(
'http://localhost:5601/foo/bar/a/b'
);
});
it('returns slash if path is just basePath', () => {
const basePath = new BasePath('/foo/bar');
expect(basePath.remove('/foo/bar')).toBe('/');
});
it('returns full path if basePath is not its own segment', () => {
const basePath = new BasePath('/foo/bar');
expect(basePath.remove('/foo/barhop')).toBe('/foo/barhop');
});
});
});

View file

@ -0,0 +1,76 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { Request } from 'hapi';
import { KibanaRequest, toRawRequest } from './router';
import { modifyUrl } from '../../utils';
const getIncomingMessage = (request: KibanaRequest | Request) =>
request instanceof KibanaRequest ? toRawRequest(request).raw.req : request.raw.req;
export class BasePath {
private readonly basePathCache = new WeakMap<ReturnType<typeof getIncomingMessage>, string>();
constructor(private readonly serverBasePath?: string) {}
public get = (request: KibanaRequest | Request) => {
const incomingMessage = getIncomingMessage(request);
const requestScopePath = this.basePathCache.get(incomingMessage) || '';
const serverBasePath = this.serverBasePath || '';
return `${serverBasePath}${requestScopePath}`;
};
// should work only for KibanaRequest as soon as spaces migrate to NP
public set = (request: KibanaRequest | Request, requestSpecificBasePath: string) => {
const incomingMessage = getIncomingMessage(request);
if (this.basePathCache.has(incomingMessage)) {
throw new Error(
'Request basePath was previously set. Setting multiple times is not supported.'
);
}
this.basePathCache.set(incomingMessage, requestSpecificBasePath);
};
public prepend = (path: string): string => {
if (!this.serverBasePath) return path;
return modifyUrl(path, parts => {
if (!parts.hostname && parts.pathname && parts.pathname.startsWith('/')) {
parts.pathname = `${this.serverBasePath}${parts.pathname}`;
}
});
};
public remove = (path: string): string => {
if (!this.serverBasePath) {
return path;
}
if (path === this.serverBasePath) {
return '/';
}
if (path.startsWith(`${this.serverBasePath}/`)) {
return path.slice(this.serverBasePath.length);
}
return path;
};
}

View file

@ -32,8 +32,6 @@ import { ByteSizeValue } from '@kbn/config-schema';
import { HttpConfig, Router } from '.';
import { loggingServiceMock } from '../logging/logging_service.mock';
import { HttpServer } from './http_server';
import { KibanaRequest } from './router';
import { httpServerMock } from './http_server.mocks';
const chance = new Chance();
@ -603,85 +601,6 @@ test('throws an error if starts without set up', async () => {
);
});
test('#getBasePathFor() returns base path associated with an incoming request', async () => {
const {
getBasePathFor,
setBasePathFor,
registerRouter,
server: innerServer,
registerOnPostAuth,
} = await server.setup(config);
const path = '/base-path';
registerOnPostAuth((req, t) => {
setBasePathFor(req, path);
return t.next();
});
const router = new Router('/');
router.get({ path: '/', validate: false }, (req, res) => res.ok({ key: getBasePathFor(req) }));
registerRouter(router);
await server.start();
await supertest(innerServer.listener)
.get('/')
.expect(200)
.then(res => {
expect(res.body).toEqual({ key: path });
});
});
test('#getBasePathFor() is based on server base path', async () => {
const configWithBasePath = {
...config,
basePath: '/bar',
};
const {
getBasePathFor,
setBasePathFor,
registerRouter,
server: innerServer,
registerOnPostAuth,
} = await server.setup(configWithBasePath);
const path = '/base-path';
registerOnPostAuth((req, t) => {
setBasePathFor(req, path);
return t.next();
});
const router = new Router('/');
router.get({ path: '/', validate: false }, (req, res) => res.ok({ key: getBasePathFor(req) }));
registerRouter(router);
await server.start();
await supertest(innerServer.listener)
.get('/')
.expect(200)
.then(res => {
expect(res.body).toEqual({ key: `${configWithBasePath.basePath}${path}` });
});
});
test('#setBasePathFor() cannot be set twice for one request', async () => {
const kibanaRequestFactory = {
from() {
return KibanaRequest.from(httpServerMock.createRawRequest());
},
};
jest.doMock('./router/request', () => ({
KibanaRequest: jest.fn(() => kibanaRequestFactory),
}));
const { setBasePathFor } = await server.setup(config);
const req = kibanaRequestFactory.from();
const setPath = () => setBasePathFor(req, '/path');
setPath();
expect(setPath).toThrowErrorMatchingInlineSnapshot(
`"Request basePath was previously set. Setting multiple times is not supported."`
);
});
const cookieOptions = {
name: 'sid',
encryptionKey: 'something_at_least_32_characters',

View file

@ -19,23 +19,20 @@
import { Request, Server, ServerOptions } from 'hapi';
import { modifyUrl } from '../../utils';
import { Logger } from '../logging';
import { HttpConfig } from './http_config';
import { createServer, getServerOptions } from './http_tools';
import { adoptToHapiAuthFormat, AuthenticationHandler } from './lifecycle/auth';
import { adoptToHapiOnPostAuthFormat, OnPostAuthHandler } from './lifecycle/on_post_auth';
import { adoptToHapiOnPreAuthFormat, OnPreAuthHandler } from './lifecycle/on_pre_auth';
import { Router, KibanaRequest, toRawRequest } from './router';
import { Router, KibanaRequest } from './router';
import {
SessionStorageCookieOptions,
createCookieSessionStorageFactory,
} from './cookie_session_storage';
import { SessionStorageFactory } from './session_storage';
import { AuthStateStorage } from './auth_state_storage';
const getIncomingMessage = (request: KibanaRequest | Request) =>
request instanceof KibanaRequest ? toRawRequest(request).raw.req : request.raw.req;
import { BasePath } from './base_path_service';
export interface HttpServerSetup {
server: Server;
@ -67,8 +64,12 @@ export interface HttpServerSetup {
* (from the first registered to the last).
*/
registerOnPostAuth: (handler: OnPostAuthHandler) => void;
getBasePathFor: (request: KibanaRequest | Request) => string;
setBasePathFor: (request: KibanaRequest | Request, basePath: string) => void;
basePath: {
get: (request: KibanaRequest | Request) => string;
set: (request: KibanaRequest | Request, basePath: string) => void;
prepend: (url: string) => string;
remove: (url: string) => string;
};
auth: {
get: AuthStateStorage['get'];
isAuthenticated: AuthStateStorage['isAuthenticated'];
@ -80,7 +81,6 @@ export class HttpServer {
private config?: HttpConfig;
private registeredRouters = new Set<Router>();
private authRegistered = false;
private basePathCache = new WeakMap<ReturnType<typeof getIncomingMessage>, string>();
private readonly authState: AuthStateStorage;
@ -101,33 +101,13 @@ export class HttpServer {
this.registeredRouters.add(router);
}
// passing hapi Request works for BWC. can be deleted once we remove legacy server.
private getBasePathFor(config: HttpConfig, request: KibanaRequest | Request) {
const incomingMessage = getIncomingMessage(request);
const requestScopePath = this.basePathCache.get(incomingMessage) || '';
const serverBasePath = config.basePath || '';
return `${serverBasePath}${requestScopePath}`;
}
// should work only for KibanaRequest as soon as spaces migrate to NP
private setBasePathFor(request: KibanaRequest | Request, basePath: string) {
const incomingMessage = getIncomingMessage(request);
if (this.basePathCache.has(incomingMessage)) {
throw new Error(
'Request basePath was previously set. Setting multiple times is not supported.'
);
}
this.basePathCache.set(incomingMessage, basePath);
}
public setup(config: HttpConfig): HttpServerSetup {
const serverOptions = getServerOptions(config);
this.server = createServer(serverOptions);
this.config = config;
this.setupBasePathRewrite(config);
const basePathService = new BasePath(config.basePath);
this.setupBasePathRewrite(config, basePathService);
return {
options: serverOptions,
@ -136,8 +116,7 @@ export class HttpServer {
registerOnPostAuth: this.registerOnPostAuth.bind(this),
registerAuth: <T>(fn: AuthenticationHandler, cookieOptions: SessionStorageCookieOptions<T>) =>
this.registerAuth(fn, cookieOptions, config.basePath),
getBasePathFor: this.getBasePathFor.bind(this, config),
setBasePathFor: this.setBasePathFor.bind(this),
basePath: basePathService,
auth: {
get: this.authState.get,
isAuthenticated: this.authState.isAuthenticated,
@ -185,26 +164,19 @@ export class HttpServer {
this.server = undefined;
}
private setupBasePathRewrite(config: HttpConfig) {
private setupBasePathRewrite(config: HttpConfig, basePathService: BasePath) {
if (config.basePath === undefined || !config.rewriteBasePath) {
return;
}
const basePath = config.basePath;
this.registerOnPreAuth((request, toolkit) => {
const newURL = modifyUrl(request.url.href!, urlParts => {
if (urlParts.pathname != null && urlParts.pathname.startsWith(basePath)) {
urlParts.pathname = urlParts.pathname.replace(basePath, '') || '/';
} else {
return {};
}
});
if (!newURL) {
return toolkit.rejected(new Error('not found'), { statusCode: 404 });
const oldUrl = request.url.href!;
const newURL = basePathService.remove(oldUrl);
const shouldRedirect = newURL !== oldUrl;
if (shouldRedirect) {
return toolkit.redirected(newURL, { forward: true });
}
return toolkit.redirected(newURL, { forward: true });
return toolkit.rejected(new Error('not found'), { statusCode: 404 });
});
}

View file

@ -19,27 +19,34 @@
import { Server, ServerOptions } from 'hapi';
import { HttpService } from './http_service';
import { HttpConfig } from './http_config';
import { HttpServerSetup } from './http_server';
import { HttpServiceSetup } from './http_service';
type ServiceSetupMockType = jest.Mocked<HttpServiceSetup> & {
basePath: jest.Mocked<HttpServiceSetup['basePath']>;
};
const createSetupContractMock = () => {
const setupContract = {
options: {} as ServerOptions,
const setupContract: ServiceSetupMockType = {
options: ({} as unknown) as ServerOptions,
// we can mock some hapi server method when we need it
server: {} as Server,
registerOnPreAuth: jest.fn(),
registerAuth: jest.fn(),
registerOnPostAuth: jest.fn(),
registerRouter: jest.fn(),
getBasePathFor: jest.fn(),
setBasePathFor: jest.fn(),
// we can mock some hapi server method when we need it
server: {} as Server,
basePath: {
get: jest.fn(),
set: jest.fn(),
prepend: jest.fn(),
remove: jest.fn(),
},
auth: {
get: jest.fn(),
isAuthenticated: jest.fn(),
},
createNewServer: async (cfg: Partial<HttpConfig>): Promise<HttpServerSetup> =>
({} as HttpServerSetup),
createNewServer: jest.fn(),
};
setupContract.createNewServer.mockResolvedValue({} as HttpServerSetup);
return setupContract;
};

View file

@ -238,7 +238,7 @@ describe('http service', () => {
});
});
describe('#getBasePathFor()/#setBasePathFor()', () => {
describe('#basePath()', () => {
let root: ReturnType<typeof kbnTestServer.createRoot>;
beforeEach(async () => {
root = kbnTestServer.createRoot();
@ -249,7 +249,7 @@ describe('http service', () => {
const reqBasePath = '/requests-specific-base-path';
const { http } = await root.setup();
http.registerOnPreAuth((req, t) => {
http.setBasePathFor(req, reqBasePath);
http.basePath.set(req, reqBasePath);
return t.next();
});
@ -260,7 +260,7 @@ describe('http service', () => {
kbnServer.server.route({
method: 'GET',
path: legacyUrl,
handler: kbnServer.newPlatform.setup.core.http.getBasePathFor,
handler: kbnServer.newPlatform.setup.core.http.basePath.get,
});
await kbnTestServer.request.get(root, legacyUrl).expect(200, reqBasePath);

View file

@ -91,8 +91,7 @@ export interface CoreSetup {
registerOnPreAuth: HttpServiceSetup['registerOnPreAuth'];
registerAuth: HttpServiceSetup['registerAuth'];
registerOnPostAuth: HttpServiceSetup['registerOnPostAuth'];
getBasePathFor: HttpServiceSetup['getBasePathFor'];
setBasePathFor: HttpServiceSetup['setBasePathFor'];
basePath: HttpServiceSetup['basePath'];
createNewServer: HttpServiceSetup['createNewServer'];
};
}

View file

@ -120,8 +120,7 @@ export function createPluginSetupContext<TPlugin, TPluginDependencies>(
registerOnPreAuth: deps.http.registerOnPreAuth,
registerAuth: deps.http.registerAuth,
registerOnPostAuth: deps.http.registerOnPostAuth,
getBasePathFor: deps.http.getBasePathFor,
setBasePathFor: deps.http.setBasePathFor,
basePath: deps.http.basePath,
createNewServer: deps.http.createNewServer,
},
};

View file

@ -87,8 +87,7 @@ export interface CoreSetup {
registerOnPreAuth: HttpServiceSetup['registerOnPreAuth'];
registerAuth: HttpServiceSetup['registerAuth'];
registerOnPostAuth: HttpServiceSetup['registerOnPostAuth'];
getBasePathFor: HttpServiceSetup['getBasePathFor'];
setBasePathFor: HttpServiceSetup['setBasePathFor'];
basePath: HttpServiceSetup['basePath'];
createNewServer: HttpServiceSetup['createNewServer'];
};
}

View file

@ -20,15 +20,20 @@
import { ParsedUrlQuery } from 'querystring';
import { format as formatUrl, parse as parseUrl, UrlObject } from 'url';
/**
* We define our own typings because the current version of @types/node
* declares properties to be optional "hostname?: string".
* Although, parse call returns "hostname: null | string".
*/
export interface URLMeaningfulParts {
auth: string | null;
hash: string | null;
hostname: string | null;
pathname: string | null;
protocol: string | null;
slashes: boolean | null;
port: string | null;
query: ParsedUrlQuery | {};
auth?: string | null;
hash?: string | null;
hostname?: string | null;
pathname?: string | null;
protocol?: string | null;
slashes?: boolean | null;
port?: string | null;
query: ParsedUrlQuery;
}
/**
@ -62,7 +67,7 @@ export interface URLMeaningfulParts {
*/
export function modifyUrl(
url: string,
urlModifier: (urlParts: URLMeaningfulParts) => Partial<URLMeaningfulParts> | undefined
urlModifier: (urlParts: URLMeaningfulParts) => Partial<URLMeaningfulParts> | void
) {
const parsed = parseUrl(url, true) as URLMeaningfulParts;

View file

@ -20,11 +20,11 @@
export function setupBasePathProvider(kbnServer) {
kbnServer.server.decorate('request', 'setBasePath', function (basePath) {
const request = this;
kbnServer.newPlatform.setup.core.http.setBasePathFor(request, basePath);
kbnServer.newPlatform.setup.core.http.basePath.set(request, basePath);
});
kbnServer.server.decorate('request', 'getBasePath', function () {
const request = this;
return kbnServer.newPlatform.setup.core.http.getBasePathFor(request);
return kbnServer.newPlatform.setup.core.http.basePath.get(request);
});
}

View file

@ -26,40 +26,40 @@ function initChrome() {
return chrome;
}
newPlatformHttp.getBasePath.mockImplementation(() => 'gotBasePath');
newPlatformHttp.prependBasePath.mockImplementation(() => 'addedToPath');
newPlatformHttp.removeBasePath.mockImplementation(() => 'removedFromPath');
newPlatformHttp.basePath.get.mockImplementation(() => 'gotBasePath');
newPlatformHttp.basePath.prepend.mockImplementation(() => 'addedToPath');
newPlatformHttp.basePath.remove.mockImplementation(() => 'removedFromPath');
beforeEach(() => {
jest.clearAllMocks();
});
describe('#getBasePath()', () => {
it('proxies to newPlatformHttp.getBasePath()', () => {
it('proxies to newPlatformHttp.basePath.get()', () => {
const chrome = initChrome();
expect(newPlatformHttp.prependBasePath).not.toHaveBeenCalled();
expect(newPlatformHttp.basePath.prepend).not.toHaveBeenCalled();
expect(chrome.getBasePath()).toBe('gotBasePath');
expect(newPlatformHttp.getBasePath).toHaveBeenCalledTimes(1);
expect(newPlatformHttp.getBasePath).toHaveBeenCalledWith();
expect(newPlatformHttp.basePath.get).toHaveBeenCalledTimes(1);
expect(newPlatformHttp.basePath.get).toHaveBeenCalledWith();
});
});
describe('#addBasePath()', () => {
it('proxies to newPlatformHttp.prependBasePath(path)', () => {
it('proxies to newPlatformHttp.basePath.prepend(path)', () => {
const chrome = initChrome();
expect(newPlatformHttp.prependBasePath).not.toHaveBeenCalled();
expect(newPlatformHttp.basePath.prepend).not.toHaveBeenCalled();
expect(chrome.addBasePath('foo/bar')).toBe('addedToPath');
expect(newPlatformHttp.prependBasePath).toHaveBeenCalledTimes(1);
expect(newPlatformHttp.prependBasePath).toHaveBeenCalledWith('foo/bar');
expect(newPlatformHttp.basePath.prepend).toHaveBeenCalledTimes(1);
expect(newPlatformHttp.basePath.prepend).toHaveBeenCalledWith('foo/bar');
});
});
describe('#removeBasePath', () => {
it('proxies to newPlatformBasePath.removeBasePath(path)', () => {
it('proxies to newPlatformBasePath.basePath.remove(path)', () => {
const chrome = initChrome();
expect(newPlatformHttp.removeBasePath).not.toHaveBeenCalled();
expect(newPlatformHttp.basePath.remove).not.toHaveBeenCalled();
expect(chrome.removeBasePath('foo/bar')).toBe('removedFromPath');
expect(newPlatformHttp.removeBasePath).toHaveBeenCalledTimes(1);
expect(newPlatformHttp.removeBasePath).toHaveBeenCalledWith('foo/bar');
expect(newPlatformHttp.basePath.remove).toHaveBeenCalledTimes(1);
expect(newPlatformHttp.basePath.remove).toHaveBeenCalledWith('foo/bar');
});
});

View file

@ -22,7 +22,7 @@ import { npSetup } from 'ui/new_platform';
const newPlatformHttp = npSetup.core.http;
export function initChromeBasePathApi(chrome: { [key: string]: any }) {
chrome.getBasePath = newPlatformHttp.getBasePath.bind(newPlatformHttp);
chrome.addBasePath = newPlatformHttp.prependBasePath.bind(newPlatformHttp);
chrome.removeBasePath = newPlatformHttp.removeBasePath.bind(newPlatformHttp);
chrome.getBasePath = newPlatformHttp.basePath.get;
chrome.addBasePath = newPlatformHttp.basePath.prepend;
chrome.removeBasePath = newPlatformHttp.basePath.remove;
}

View file

@ -79,7 +79,7 @@ export const configureAppAngularModule = (angularModule: IModule) => {
const getEsUrl = (newPlatform: InternalCoreStart) => {
const a = document.createElement('a');
a.href = newPlatform.http.prependBasePath('/elasticsearch');
a.href = newPlatform.http.basePath.prepend('/elasticsearch');
const protocolPort = /https/.test(a.protocol) ? 443 : 80;
const port = a.port || protocolPort;
return {

View file

@ -17,7 +17,7 @@
* under the License.
*/
import { modifyUrl } from '../../../../../core/public/utils';
import { modifyUrl } from '../../../../../core/utils';
import { toastNotifications } from '../toasts';
const APP_REDIRECT_MESSAGE_PARAM = 'app_redirect_message';

View file

@ -19,7 +19,7 @@
import { parse } from 'url';
import { modifyUrl } from '../../../../core/public/utils';
import { modifyUrl } from '../../../../core/utils';
import { prependPath } from './prepend_path';
interface Options {