Skip to content

Commit

Permalink
Add request history feature
Browse files Browse the repository at this point in the history
  • Loading branch information
Huachao committed Apr 27, 2016
1 parent 3f3f4d9 commit 1f9d364
Show file tree
Hide file tree
Showing 10 changed files with 281 additions and 85 deletions.
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ authorization: token xxx
</request>
```

## Request History
![request-history](images/request-history.png)
Each time we sent a http request, the request details(method, url, headers and body) would be persisted into file. By using shortcut `Ctrl+Alt+H`, or press `F1` and then select/type `Rest Client: Request History`, you can view the last __50__ request items in the time reversing order, you can select any request you wish to trigger again. After specified request history item is selected, the request details would be displayed in a temp file, you can view the request details or follow previous step to trigger the request again.

## Settings
* `rest-client.clearoutput`: Clear previous output for each request. (Default is __false__)
* `rest-client.followredirect`: Follow HTTP 3xx responses as redirects. (Default is __true__)
Expand All @@ -73,6 +77,9 @@ authorization: token xxx
[MIT License](LICENSE)

## Change Log
### 0.2.0
* Add http request history

### 0.1.1
* Update image in README.md

Expand Down
Binary file added images/request-history.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 13 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "rest-client",
"displayName": "REST Client",
"description": "REST Client for Visual Studio Code",
"version": "0.1.1",
"version": "0.2.0",
"publisher": "humao",
"author": {
"name": "Huachao Mao",
Expand Down Expand Up @@ -34,20 +34,29 @@
"Http"
],
"activationEvents": [
"onCommand:rest-client.request"
"onCommand:rest-client.request",
"onCommand:rest-client.history"
],
"main": "./out/src/extension",
"contributes": {
"commands": [
{
"command": "rest-client.request",
"title": "Rest Client: Send Request"
},
{
"command": "rest-client.history",
"title": "Rest Client: Request History"
}
],
"keybindings": [
{
"command": "rest-client.request",
"key": "ctrl+alt+r"
},
{
"command": "rest-client.history",
"key": "ctrl+alt+h"
}
],
"configuration": {
Expand Down Expand Up @@ -82,6 +91,7 @@
"vscode": "^0.11.0"
},
"dependencies": {
"request": "^2.69.0"
"request": "^2.69.0",
"tmp": "^0.0.28"
}
}
5 changes: 5 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
'use strict';

export const ExtensionId: string = 'humao.rest-client';
export const HistoryFileName: string = 'history.json';
export const HistoryItemsMaxCount: number = 50;
83 changes: 83 additions & 0 deletions src/controllers/historyController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
"use strict";

import { window, workspace, QuickPickItem, OutputChannel } from 'vscode';
import { PersistUtility } from '../persistUtility'
import { HttpRequest } from '../models/httpRequest'
import { HistoryQuickPickItem } from '../models/historyQuickPickItem'
import { EOL } from 'os';
import * as fs from 'fs'

var tmp = require('tmp')

export class HistoryController {
private _outputChannel: OutputChannel;

constructor() {
this._outputChannel = window.createOutputChannel('REST');
}

run(): any {
PersistUtility.load().then(requests => {
if (!requests || requests.length <= 0) {
window.showInformationMessage("No request history items are found!");
return;
}
var itemPickList: HistoryQuickPickItem[] = requests.map(request => {
// TODO: add headers and body in pick item?
let item = new HistoryQuickPickItem();
item.label = `${request.method.toUpperCase()} ${request.url}`;
item.rawRequest = request;
return item;
});

window.showQuickPick(itemPickList, { placeHolder: "" }).then(item => {
if (!item) {
return;
}
this.createRequestInTempFile(item.rawRequest).then(path => {
workspace.openTextDocument(path).then(d => {
window.showTextDocument(d);
});
});
})
}).catch(error => this.errorHandler(error));
}

private createRequestInTempFile(request: HttpRequest): Promise<string> {
return new Promise((resolve, reject) => {
tmp.file({ prefix: 'vscode-restclient-' }, function _tempFileCreated(err, tmpFilePath, fd) {
if (err) {
reject(err);
return;
}
let output = `${request.method.toUpperCase()} ${request.url}${EOL}`;
if (request.headers) {
for (var header in request.headers) {
if (request.headers.hasOwnProperty(header)) {
var value = request.headers[header];
output += `${header}: ${value}${EOL}`;
}
}
}
if (request.body) {
output += `${EOL}${request.body}`;
}
fs.writeFile(tmpFilePath, output, error => {
reject(error);
return;
});
resolve(tmpFilePath);
});
});
}

private errorHandler(error: any) {
this._outputChannel.appendLine(error);
this._outputChannel.show();
window.showErrorMessage("There was an error, please view details in output log");
}

dispose() {
this._outputChannel.dispose();
}
}
97 changes: 97 additions & 0 deletions src/controllers/requestController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
"use strict";

import { window, workspace, Uri, StatusBarItem, StatusBarAlignment, OutputChannel } from 'vscode';
import { RequestParser } from '../parser'
import { MimeUtility } from '../mimeUtility'
import { HttpClient } from '../httpClient'
import { HttpRequest } from '../models/httpRequest'
import { RestClientSettings } from '../models/configurationSettings'
import { PersistUtility } from '../persistUtility'

export class RequestController {
private _outputChannel: OutputChannel;
private _statusBarItem: StatusBarItem;
private _restClientSettings: RestClientSettings;
private _httpClient: HttpClient;

constructor() {
this._outputChannel = window.createOutputChannel('REST');
this._statusBarItem = window.createStatusBarItem(StatusBarAlignment.Left);
this._restClientSettings = new RestClientSettings();
this._httpClient = new HttpClient(this._restClientSettings);
}

run() {
let editor = window.activeTextEditor;
if (!editor || !editor.document) {
return;
}

// Get selected text of selected lines or full document
let selectedText: string;
if (editor.selection.isEmpty) {
selectedText = editor.document.getText();
} else {
selectedText = editor.document.getText(editor.selection);
}

if (selectedText === '') {
return;
}

if (this._restClientSettings.clearOutput) {
this._outputChannel.clear();
}

// clear status bar
this._statusBarItem.text = `$(cloud-upload)`;
this._statusBarItem.show();

// parse http request
let httpRequest = RequestParser.parseHttpRequest(selectedText);
if (!httpRequest) {
return;
}

// set http request
this._httpClient.send(httpRequest)
.then(response => {
let output = `HTTP/${response.httpVersion} ${response.statusCode} ${response.statusMessage}\n`
for (var header in response.headers) {
if (response.headers.hasOwnProperty(header)) {
var value = response.headers[header];
output += `${header}: ${value}\n`
}
}

let body = response.body;
let contentType = response.headers['content-type'];
if (contentType) {
let type = MimeUtility.parse(contentType).type;
if (type === 'application/json') {
body = JSON.stringify(JSON.parse(body), null, 4);
}
}

output += `\n${body}`;
this._outputChannel.appendLine(`${output}\n`);
this._outputChannel.show(true);

this._statusBarItem.text = ` $(clock) ${response.elapsedMillionSeconds}ms`;
this._statusBarItem.tooltip = 'duration';

// persist to history json file
PersistUtility.save(httpRequest);
})
.catch(error => {
this._statusBarItem.text = '';
this._outputChannel.appendLine(`${error}\n`);
this._outputChannel.show(true);
});
}

dispose() {
this._outputChannel.dispose();
this._statusBarItem.dispose();
}
}
90 changes: 9 additions & 81 deletions src/extension.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
'use strict';
// The module 'vscode' contains the VS Code extensibility API
// Import the module and reference it with the alias vscode in your code below
import { ExtensionContext, commands, window, workspace, Uri, StatusBarAlignment } from 'vscode';
import { RequestParser } from './parser'
import { MimeUtility } from './mimeUtility'
import { HttpClient } from './httpClient'
import { HttpRequest } from './models/httpRequest'
import { RestClientSettings } from './models/configurationSettings'
import { ExtensionContext, commands, Disposable } from 'vscode';
import { RequestController } from './controllers/requestController'
import { HistoryController } from './controllers/historyController'

// this method is called when your extension is activated
// your extension is activated the very first time the command is executed
Expand All @@ -16,81 +13,12 @@ export function activate(context: ExtensionContext) {
// This line of code will only be executed once when your extension is activated
console.log('Congratulations, your extension "rest-client" is now active!');

let outChannel = window.createOutputChannel('REST');
let statusBarItem = window.createStatusBarItem(StatusBarAlignment.Left)
let restClientSettings = new RestClientSettings();

// The command has been defined in the package.json file
// Now provide the implementation of the command with registerCommand
// The commandId parameter must match the command field in package.json
let disposable = commands.registerCommand('rest-client.request', () => {
let editor = window.activeTextEditor;
if (!editor || !editor.document) {
return;
}

// Get selected text of selected lines or full document
let selectedText: string;
if (editor.selection.isEmpty) {
selectedText = editor.document.getText();
} else {
selectedText = editor.document.getText(editor.selection);
}

if (selectedText === '') {
return;
}

if (restClientSettings.clearOutput) {
outChannel.clear();
}

// clear status bar
statusBarItem.text = `$(cloud-upload)`;
statusBarItem.show();

// parse http request
let httpRequest = RequestParser.parseHttpRequest(selectedText);
if (!httpRequest) {
return;
}

// set http request
let httpClient = new HttpClient(restClientSettings);
httpClient.send(httpRequest)
.then(response => {
let output = `HTTP/${response.httpVersion} ${response.statusCode} ${response.statusMessage}\n`
for (var header in response.headers) {
if (response.headers.hasOwnProperty(header)) {
var value = response.headers[header];
output += `${header}: ${value}\n`
}
}

let body = response.body;
let contentType = response.headers['content-type'];
if (contentType) {
let type = MimeUtility.parse(contentType).type;
if (type === 'application/json') {
body = JSON.stringify(JSON.parse(body), null, 4);
}
}

output += `\n${body}`;
outChannel.appendLine(`${output}\n`);
outChannel.show(true);

statusBarItem.text = ` $(clock) ${response.elapsedMillionSeconds}ms`;
statusBarItem.tooltip = 'duration';
})
.catch(error => {
statusBarItem.text = '';
outChannel.appendLine(`${error}\n`);
outChannel.show(true);
});
});

context.subscriptions.push(disposable);
let requestController = new RequestController();
let historyController = new HistoryController();
context.subscriptions.push(requestController);
context.subscriptions.push(historyController);
context.subscriptions.push(commands.registerCommand('rest-client.request', () => requestController.run()));
context.subscriptions.push(commands.registerCommand('rest-client.history', () => historyController.run()));
}

// this method is called when your extension is deactivated
Expand Down
2 changes: 1 addition & 1 deletion src/httpClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export class HttpClient {
};

if (!options.headers) {
options.headers = {};
options.headers = httpRequest.headers = {};
}

// add default user agent if not specified
Expand Down
11 changes: 11 additions & 0 deletions src/models/historyQuickPickItem.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
"use strict";

import { QuickPickItem } from 'vscode';
import { HttpRequest } from '../models/httpRequest'

export class HistoryQuickPickItem implements QuickPickItem {
label: string;
description: string;
detail: string;
rawRequest: HttpRequest;
}
Loading

0 comments on commit 1f9d364

Please sign in to comment.