提交 ae1d5457 编写于 作者: J Joao Moreno

improve request service interface

上级 492dc4a1
......@@ -108,6 +108,23 @@ export function download(filePath: string, context: IRequestContext): TPromise<v
});
}
export function text(context: IRequestContext): TPromise<string> {
return new Promise((c, e) => {
if (!isSuccess(context)) {
return e('Server returned ' + context.res.statusCode);
}
if (hasNoContent(context)) {
return c(null);
}
let buffer: string[] = [];
context.stream.on('data', d => buffer.push(d));
context.stream.on('end', () => c(buffer.join('')));
context.stream.on('error', e);
});
}
export function json<T>(context: IRequestContext): TPromise<T> {
return new Promise((c, e) => {
if (!isSuccess(context)) {
......
......@@ -25,8 +25,8 @@ import { ExtensionGalleryService } from 'vs/platform/extensionManagement/node/ex
import { ITelemetryService, combinedAppender, NullTelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { TelemetryService, ITelemetryServiceConfig } from 'vs/platform/telemetry/common/telemetryService';
import { resolveCommonProperties } from 'vs/platform/telemetry/node/commonProperties';
import { IRequestService2 } from 'vs/platform/request/common/request';
import { RequestService2 } from 'vs/platform/request/node/requestService2';
import { IRequestService } from 'vs/platform/request/common/request';
import { RequestService } from 'vs/platform/request/node/requestService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { NodeConfigurationService } from 'vs/platform/configuration/node/nodeConfigurationService';
import { AppInsightsAppender } from 'vs/platform/telemetry/node/appInsightsAppender';
......@@ -157,7 +157,7 @@ export function main(argv: ParsedArgs): TPromise<void> {
const services = new ServiceCollection();
services.set(IEventService, new SyncDescriptor(EventService));
services.set(IConfigurationService, new SyncDescriptor(NodeConfigurationService));
services.set(IRequestService2, new SyncDescriptor(RequestService2));
services.set(IRequestService, new SyncDescriptor(RequestService));
services.set(IExtensionManagementService, new SyncDescriptor(ExtensionManagementService));
services.set(IExtensionGalleryService, new SyncDescriptor(ExtensionGalleryService));
......
......@@ -22,8 +22,8 @@ import { ExtensionManagementService } from 'vs/platform/extensionManagement/node
import { ExtensionGalleryService } from 'vs/platform/extensionManagement/node/extensionGalleryService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { NodeConfigurationService } from 'vs/platform/configuration/node/nodeConfigurationService';
import { IRequestService2 } from 'vs/platform/request/common/request';
import { RequestService2 } from 'vs/platform/request/node/requestService2';
import { IRequestService } from 'vs/platform/request/common/request';
import { RequestService } from 'vs/platform/request/node/requestService';
import { ITelemetryService, combinedAppender, NullTelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { resolveCommonProperties } from 'vs/platform/telemetry/node/commonProperties';
import { TelemetryAppenderChannel } from 'vs/platform/telemetry/common/telemetryIpc';
......@@ -59,7 +59,7 @@ function main(server: Server): void {
services.set(IEventService, new SyncDescriptor(EventService));
services.set(IEnvironmentService, new SyncDescriptor(EnvironmentService));
services.set(IConfigurationService, new SyncDescriptor(NodeConfigurationService));
services.set(IRequestService2, new SyncDescriptor(RequestService2));
services.set(IRequestService, new SyncDescriptor(RequestService));
const instantiationService = new InstantiationService(services);
......
......@@ -10,7 +10,7 @@ import { TPromise } from 'vs/base/common/winjs.base';
import { IGalleryExtension, IExtensionGalleryService, IQueryOptions, SortBy, SortOrder, IExtensionManifest } from 'vs/platform/extensionManagement/common/extensionManagement';
import { isUndefined } from 'vs/base/common/types';
import { assign, getOrDefault } from 'vs/base/common/objects';
import { IRequestService2 } from 'vs/platform/request/common/request';
import { IRequestService } from 'vs/platform/request/common/request';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IPager } from 'vs/base/common/paging';
import { download, json } from 'vs/base/node/request';
......@@ -227,7 +227,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
}
constructor(
@IRequestService2 private requestService: IRequestService2,
@IRequestService private requestService: IRequestService,
@ITelemetryService private telemetryService: ITelemetryService,
@IConfigurationService private configurationService: IConfigurationService
) {
......
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import URI from 'vs/base/common/uri';
import {TPromise} from 'vs/base/common/winjs.base';
import {xhr, IXHROptions} from 'vs/base/common/network';
import strings = require('vs/base/common/strings');
import Timer = require('vs/base/common/timer');
import Async = require('vs/base/common/async');
import http = require('vs/base/common/http');
import objects = require('vs/base/common/objects');
import {IRequestService} from 'vs/platform/request/common/request';
import {ITelemetryService, NullTelemetryService} from 'vs/platform/telemetry/common/telemetry';
import {IWorkspaceContextService} from 'vs/platform/workspace/common/workspace';
/**
* Simple IRequestService implementation to allow sharing of this service implementation
* between different layers of the platform.
*/
export class BaseRequestService implements IRequestService {
public _serviceBrand: any;
private _serviceMap: { [service: string]: string; };
private _origin: string;
protected _telemetryService: ITelemetryService;
constructor(contextService: IWorkspaceContextService, telemetryService: ITelemetryService = NullTelemetryService) {
let workspaceUri: string = null;
let workspace = contextService.getWorkspace();
this._serviceMap = (<any>workspace) || Object.create(null);
this._telemetryService = telemetryService;
if (workspace) {
workspaceUri = strings.rtrim(workspace.resource.toString(), '/') + '/';
}
this.computeOrigin(workspaceUri);
}
private computeOrigin(workspaceUri: string): void {
if (workspaceUri) {
// Find root server URL from configuration
this._origin = workspaceUri;
let urlPath = URI.parse(this._origin).path;
if (urlPath && urlPath.length > 0) {
this._origin = this._origin.substring(0, this._origin.length - urlPath.length + 1);
}
if (!strings.endsWith(this._origin, '/')) {
this._origin += '/';
}
} else {
this._origin = '/'; // Configuration not provided, fallback to default
}
}
protected makeCrossOriginRequest(options: http.IXHROptions): TPromise<http.IXHRResponse> {
return null;
}
public makeRequest(options: http.IXHROptions): TPromise<http.IXHRResponse> {
let timer: Timer.ITimerEvent = Timer.nullEvent;
let isXhrRequestCORS = false;
let url = options.url;
if (!url) {
throw new Error('IRequestService.makeRequest: Url is required');
}
if ((strings.startsWith(url, 'http://') || strings.startsWith(url, 'https://')) && this._origin && !strings.startsWith(url, this._origin)) {
let coPromise = this.makeCrossOriginRequest(options);
if (coPromise) {
return coPromise;
}
isXhrRequestCORS = true;
}
let xhrOptions = <IXHROptions>options;
let xhrOptionsPromise = TPromise.as(undefined);
if (!isXhrRequestCORS) {
xhrOptions = this._telemetryService.getTelemetryInfo().then(info => {
let additionalHeaders = {};
additionalHeaders['X-TelemetrySession'] = info.sessionId;
additionalHeaders['X-Requested-With'] = 'XMLHttpRequest';
xhrOptions.headers = objects.mixin(xhrOptions.headers, additionalHeaders);
});
}
if (options.timeout) {
xhrOptions.customRequestInitializer = function(xhrRequest: XMLHttpRequest) {
xhrRequest.timeout = options.timeout;
};
}
return xhrOptionsPromise.then(() => {
return Async.always(xhr(xhrOptions), ((xhr: XMLHttpRequest) => {
if (timer.data) {
timer.data.status = xhr.status;
}
timer.stop();
}));
});
}
}
......@@ -5,30 +5,17 @@
'use strict';
import { localize } from 'vs/nls';
import http = require('vs/base/common/http');
import { TPromise } from 'vs/base/common/winjs.base';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IRequestOptions, IRequestContext } from 'vs/base/node/request';
import { IConfigurationRegistry, Extensions } from 'vs/platform/configuration/common/configurationRegistry';
import { Registry } from 'vs/platform/platform';
export const IRequestService = createDecorator<IRequestService>('requestService');
export const IRequestService = createDecorator<IRequestService>('requestService2');
export interface IRequestService {
_serviceBrand: any;
/**
* Wraps the call into WinJS.XHR to allow for mocking and telemetry. Use this instead
* of calling WinJS.XHR directly.
*/
makeRequest(options: http.IXHROptions): TPromise<http.IXHRResponse>;
}
export const IRequestService2 = createDecorator<IRequestService>('requestService2');
export interface IRequestService2 {
_serviceBrand: any;
request(options: IRequestOptions): TPromise<IRequestContext>;
}
......
......@@ -8,14 +8,14 @@ import { TPromise } from 'vs/base/common/winjs.base';
import { IDisposable } from 'vs/base/common/lifecycle';
import { IRequestOptions, IRequestContext, request } from 'vs/base/node/request';
import { getProxyAgent } from 'vs/base/node/proxy';
import { IRequestService2, IHTTPConfiguration } from 'vs/platform/request/common/request';
import { IRequestService, IHTTPConfiguration } from 'vs/platform/request/common/request';
import { IConfigurationService, IConfigurationServiceEvent } from 'vs/platform/configuration/common/configuration';
/**
* This service exposes the `request` API, while using the global
* or configured proxy settings.
*/
export class RequestService2 implements IRequestService2 {
export class RequestService implements IRequestService {
_serviceBrand: any;
......
......@@ -28,7 +28,6 @@ import {IEditorInput, IEditorModel, Position, Direction, IEditor, IResourceInput
import {IEventService} from 'vs/platform/event/common/event';
import {IUntitledEditorService} from 'vs/workbench/services/untitled/common/untitledEditorService';
import {IMessageService, IConfirmation} from 'vs/platform/message/common/message';
import {BaseRequestService} from 'vs/platform/request/common/baseRequestService';
import {IWorkspace, IConfiguration} from 'vs/platform/workspace/common/workspace';
import {ILifecycleService, ShutdownEvent} from 'vs/platform/lifecycle/common/lifecycle';
import {EditorStacksModel} from 'vs/workbench/common/editor/editorStacksModel';
......@@ -256,48 +255,6 @@ export class TestStorageService extends EventEmitter.EventEmitter implements ISt
}
}
export class TestRequestService extends BaseRequestService {
constructor(workspace = TestWorkspace) {
super(new TestContextService(), NullTelemetryService);
}
}
export interface ICustomResponse {
responseText: string;
getResponseHeader: (key: string) => string;
}
export interface IMockRequestHandler {
(url: string): string | ICustomResponse;
}
export class MockRequestService extends BaseRequestService {
constructor(workspace: any, private handler: IMockRequestHandler) {
super(new TestContextService(), NullTelemetryService);
}
public makeRequest(options: http.IXHROptions): TPromise<http.IXHRResponse> {
let data = this.handler(options.url);
if (!data) {
return super.makeRequest(options);
}
let isString = Types.isString(data);
let responseText = isString ? <string>data : (<ICustomResponse>data).responseText;
let getResponseHeader = isString ? () => '' : (<ICustomResponse>data).getResponseHeader;
return TPromise.as<http.IXHRResponse>({
responseText: responseText,
status: 200,
readyState: 4,
getResponseHeader: getResponseHeader
});
}
}
export class TestUntitledEditorService implements IUntitledEditorService {
public _serviceBrand: any;
......@@ -518,7 +475,7 @@ export class TestQuickOpenService implements QuickOpenService.IQuickOpenService
}
close(): void {
}
show(prefix?: string, options?: any): Promise {
......
......@@ -30,9 +30,8 @@ import {Update} from 'vs/workbench/electron-browser/update';
import {WorkspaceStats} from 'vs/workbench/services/telemetry/common/workspaceStats';
import {IWindowService, WindowService} from 'vs/workbench/services/window/electron-browser/windowService';
import {MessageService} from 'vs/workbench/services/message/electron-browser/messageService';
import {IRequestService,IRequestService2} from 'vs/platform/request/common/request';
import {RequestService} from 'vs/workbench/services/request/node/requestService';
import {RequestService2} from 'vs/platform/request/node/requestService2';
import {IRequestService} from 'vs/platform/request/common/request';
import {RequestService} from 'vs/platform/request/node/requestService';
import {IConfigurationService} from 'vs/platform/configuration/common/configuration';
import {FileService} from 'vs/workbench/services/files/electron-browser/fileService';
import {SearchService} from 'vs/workbench/services/search/node/searchService';
......@@ -294,12 +293,9 @@ export class WorkbenchShell {
this.contextViewService = instantiationService.createInstance(ContextViewService, this.container);
serviceCollection.set(IContextViewService, this.contextViewService);
const requestService = disposables.add(instantiationService.createInstance(RequestService));
const requestService = instantiationService.createInstance(RequestService);
serviceCollection.set(IRequestService, requestService);
const requestService2 = instantiationService.createInstance(RequestService2);
serviceCollection.set(IRequestService2, requestService2);
const markerService = instantiationService.createInstance(MarkerService);
serviceCollection.set(IMarkerService, markerService);
......
......@@ -12,7 +12,6 @@ import {Action} from 'vs/base/common/actions';
import {ipcRenderer as ipc, shell} from 'electron';
import {IMessageService} from 'vs/platform/message/common/message';
import {IWorkspaceContextService} from 'vs/workbench/services/workspace/common/contextService';
import {IRequestService} from 'vs/platform/request/common/request';
interface IUpdate {
releaseNotes: string;
......@@ -56,8 +55,7 @@ export class Update {
constructor(
@IWorkspaceContextService private contextService : IWorkspaceContextService,
@IMessageService private messageService : IMessageService,
@IRequestService private requestService : IRequestService
@IMessageService private messageService : IMessageService
) {
const env = this.contextService.getConfiguration().env;
......
......@@ -18,6 +18,7 @@ import { IViewlet } from 'vs/workbench/common/viewlet';
import { IViewletService } from 'vs/workbench/services/viewlet/common/viewletService';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { text } from 'vs/base/node/request';
import { IRequestService } from 'vs/platform/request/common/request';
import { IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IThemeService } from 'vs/workbench/services/themes/common/themeService';
......@@ -174,8 +175,8 @@ export class ExtensionEditor extends BaseEditor {
if (extension.readmeUrl) {
promise = promise
.then(() => addClass(this.body, 'loading'))
.then(() => this.requestService.makeRequest({ url: extension.readmeUrl }))
.then(response => response.responseText)
.then(() => this.requestService.request({ url: extension.readmeUrl }))
.then(text)
.then(marked.parse)
.then<void>(body => {
const webview = new WebView(
......
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { TPromise, Promise } from 'vs/base/common/winjs.base';
import { xhr } from 'vs/base/common/network';
import strings = require('vs/base/common/strings');
import nls = require('vs/nls');
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IHTTPConfiguration } from 'vs/platform/request/common/request';
import { BaseRequestService } from 'vs/platform/request/common/baseRequestService';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { assign } from 'vs/base/common/objects';
import { IXHROptions, IXHRResponse } from 'vs/base/common/http';
import { request } from 'vs/base/node/request';
import { getProxyAgent } from 'vs/base/node/proxy';
export class RequestService extends BaseRequestService {
private disposables: IDisposable[];
private proxyUrl: string = null;
private strictSSL: boolean = true;
constructor(
@IWorkspaceContextService contextService: IWorkspaceContextService,
@IConfigurationService configurationService: IConfigurationService,
@ITelemetryService telemetryService?: ITelemetryService
) {
super(contextService, telemetryService);
this.disposables = [];
const config = configurationService.getConfiguration<IHTTPConfiguration>();
this.configure(config);
const disposable = configurationService.onDidUpdateConfiguration(e => this.configure(e.config));
this.disposables.push(disposable);
}
private configure(config: IHTTPConfiguration) {
this.proxyUrl = config.http && config.http.proxy;
this.strictSSL = config.http && config.http.proxyStrictSSL;
}
makeRequest(options: IXHROptions): TPromise<IXHRResponse> {
let url = options.url;
if (!url) {
throw new Error('IRequestService.makeRequest: Url is required.');
}
// Support file:// in native environment through XHR
if (strings.startsWith(url, 'file://')) {
return xhr(options).then(null, (xhr: XMLHttpRequest) => {
if (xhr.status === 0 && xhr.responseText) {
return xhr; // loading resources locally returns a status of 0 which in WinJS is an error so we need to handle it here
}
return <any>Promise.wrapError({ status: 404, responseText: nls.localize('localFileNotFound', "File not found.")});
});
}
return super.makeRequest(options);
}
protected makeCrossOriginRequest(options: IXHROptions): TPromise<IXHRResponse> {
const { proxyUrl, strictSSL } = this;
const agent = getProxyAgent(options.url, { proxyUrl, strictSSL });
options = assign({}, options);
options = assign(options, { agent, strictSSL });
return request(options).then(result => new TPromise<IXHRResponse>((c, e, p) => {
const { res, stream } = result;
const data: string[] = [];
stream.on('data', c => data.push(c));
stream.on('end', () => {
const status = res.statusCode;
if (options.followRedirects > 0 && (status >= 300 && status <= 303 || status === 307)) {
let location = res.headers['location'];
if (location) {
let newOptions = {
type: options.type, url: location, user: options.user, password: options.password, responseType: options.responseType, headers: options.headers,
timeout: options.timeout, followRedirects: options.followRedirects - 1, data: options.data
};
xhr(newOptions).done(c, e, p);
return;
}
}
const response: IXHRResponse = {
responseText: data.join(''),
status,
getResponseHeader: header => res.headers[header],
readyState: 4
};
if ((status >= 200 && status < 300) || status === 1223) {
c(response);
} else {
e(response);
}
});
}, err => {
let message: string;
if (agent) {
message = 'Unable to to connect to ' + options.url + ' through a proxy . Error: ' + err.message;
} else {
message = 'Unable to to connect to ' + options.url + '. Error: ' + err.message;
}
return TPromise.wrapError<IXHRResponse>({
responseText: message,
status: 404
});
}));
}
dispose(): void {
this.disposables = dispose(this.disposables);
}
}
\ No newline at end of file
......@@ -12,7 +12,6 @@ import {Promise, TPromise} from 'vs/base/common/winjs.base';
import paths = require('vs/base/common/paths');
import {IEditorControl} from 'vs/platform/editor/common/editor';
import URI from 'vs/base/common/uri';
import {IRequestService} from 'vs/platform/request/common/request';
import {IModelService} from 'vs/editor/common/services/modelService';
import {IModeService} from 'vs/editor/common/services/modeService';
import {IWorkspaceContextService} from 'vs/platform/workspace/common/workspace';
......@@ -29,9 +28,8 @@ import {StringEditorModel} from 'vs/workbench/common/editor/stringEditorModel';
import {FileEditorInput} from 'vs/workbench/parts/files/common/editors/fileEditorInput';
import {TextFileEditorModel} from 'vs/workbench/parts/files/common/editors/textFileEditorModel';
import {ITextFileService} from 'vs/workbench/parts/files/common/files';
import {createMockModelService, TestTextFileService, TestEventService, TestPartService, TestStorageService, TestConfigurationService, TestRequestService, TestContextService, TestWorkspace, TestEditorService, MockRequestService} from 'vs/test/utils/servicesTestUtils';
import {createMockModelService, TestTextFileService, TestEventService, TestPartService, TestStorageService, TestConfigurationService, TestContextService, TestWorkspace, TestEditorService} from 'vs/test/utils/servicesTestUtils';
import {Viewlet} from 'vs/workbench/browser/viewlet';
import {EventType} from 'vs/workbench/common/events';
import {IPanel} from 'vs/workbench/common/panel';
import {ITelemetryService, NullTelemetryService} from 'vs/platform/telemetry/common/telemetry';
import {IUntitledEditorService, UntitledEditorService} from 'vs/workbench/services/untitled/common/untitledEditorService';
......@@ -337,25 +335,10 @@ suite('Workbench UI Services', () => {
let editorService = new TestEditorService(function () { });
let eventService = new TestEventService();
let contextService = new TestContextService(TestWorkspace);
let requestService = new MockRequestService(TestWorkspace, (url) => {
if (/index\.html$/.test(url)) {
return {
responseText: 'Hello Html',
getResponseHeader: key => ({
'content-length': '1000',
'last-modified': new Date().toUTCString(),
'content-type': 'text/html'
})[key.toLowerCase()]
};
}
return null;
});
let instantiationService= new TestInstantiationService();
instantiationService.stub(IEventService, eventService);
instantiationService.stub(IWorkspaceContextService, contextService);
instantiationService.stub(IRequestService, requestService);
instantiationService.stub(ITelemetryService);
instantiationService.stub(IConfigurationService, new TestConfigurationService());
instantiationService.stub(IUntitledEditorService, instantiationService.createInstance(UntitledEditorService));
......@@ -433,12 +416,10 @@ suite('Workbench UI Services', () => {
let editorService = new TestEditorService(function () { });
let contextService = new TestContextService(TestWorkspace);
let eventService = new TestEventService();
let requestService = new TestRequestService();
let telemetryService = NullTelemetryService;
let instantiationService = new TestInstantiationService();
instantiationService.stub(IEventService, eventService);
instantiationService.stub(IRequestService, requestService);
instantiationService.stub(ITelemetryService, telemetryService);
instantiationService.stub(IStorageService, new TestStorageService());
instantiationService.stub(IUntitledEditorService, instantiationService.createInstance(UntitledEditorService));
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册