JS: Fix http batch store (#1885)

There were a few issues here:

1. The version header was not always passed to fetch
2. HTTP headers are case insensitive and Node an Fetch uses lower case.
3. The old code used a Map instead of an object as map. Node and Fetch
   uses an object as map. Now we just pass along the response headers.

Fixes #1881
Closes #1880 (which this is partially based in)
This commit is contained in:
Erik Arvidsson
2016-06-23 17:41:56 +08:00
committed by GitHub
parent 123544c4f7
commit ebd9a88d23
7 changed files with 38 additions and 42 deletions

View File

@@ -29,7 +29,7 @@ type URLParams interface {
type Handler func(w http.ResponseWriter, req *http.Request, ps URLParams, cs chunks.ChunkStore)
// NomsVersionHeader is the name of the header that Noms clients and servers must set in every request/response.
const NomsVersionHeader = "X-Noms-Vers"
const NomsVersionHeader = "x-noms-vers"
var (
// HandleWriteValue is meant to handle HTTP POST requests to the writeValue/ server endpoint. The payload should be an appropriately-ordered sequence of Chunks to be validated and stored on the server.

View File

@@ -1,7 +1,7 @@
{
"name": "@attic/noms",
"license": "Apache-2.0",
"version": "47.0.0",
"version": "48.0.0",
"description": "Noms JS SDK",
"repository": "https://github.com/attic-labs/noms",
"main": "dist/commonjs/noms.js",

View File

@@ -9,13 +9,12 @@ export type FetchOptions = {
method?: string,
body?: any,
headers?: {[key: string]: string},
respHeaders?: string[],
withCredentials? : boolean,
};
type TextResponse = {headers: Map<string, string>, buf: string}
type BufResponse = {headers: Map<string, string>, buf: Uint8Array}
type Response<T> = {headers: Map<string, string>, buf: T}
type Response<T> = {headers: {[key: string]: string}, buf: T};
type TextResponse = Response<string>;
type BufResponse = Response<Uint8Array>;
function fetch<T>(url: string, responseType: string, options: FetchOptions = {}):
Promise<Response<T>> {
@@ -26,15 +25,9 @@ function fetch<T>(url: string, responseType: string, options: FetchOptions = {})
const p = new Promise((res, rej) => {
xhr.onloadend = () => {
if (xhr.status >= 200 && xhr.status < 300) {
const headers = new Map();
if (options.respHeaders) {
for (const header of options.respHeaders) {
headers.set(header, res.headers[header]);
}
}
res({headers: headers, buf: xhr.response});
res({headers: res.headers, buf: xhr.response});
} else {
rej(xhr.status);
rej(new Error(`HTTP Error: ${xhr.status}`));
}
};
});
@@ -53,7 +46,7 @@ function fetch<T>(url: string, responseType: string, options: FetchOptions = {})
export function fetchText(url: string, options: FetchOptions = {}): Promise<TextResponse> {
if (self.fetch) {
return self.fetch(url, options)
.then(resp => ({headers: new Map(resp.headers), buf: resp.text()}));
.then(resp => ({headers: resp.headers, buf: resp.text()}));
}
return fetch(url, 'text', options);
@@ -62,8 +55,7 @@ export function fetchText(url: string, options: FetchOptions = {}): Promise<Text
export function fetchUint8Array(url: string, options: FetchOptions = {}): Promise<BufResponse> {
if (self.fetch) {
return self.fetch(url, options)
.then(resp => [resp.headers, resp.arrayBuffer()])
.then((headers, ar) => ({headers: headers, buf: new Uint8Array(ar)}));
.then(resp => ({headers: resp.headers, buf: new Uint8Array(resp.arrayBuffer())}));
}
return fetch(url, 'arraybuffer', options);

View File

@@ -12,12 +12,12 @@ export type FetchOptions = {
method?: string,
body?: any,
headers?: {[key: string]: string},
respHeaders?: string[],
withCredentials? : boolean,
};
type TextResponse = {headers: Map<string, string>, buf: string}
type BufResponse = {headers: Map<string, string>, buf: Uint8Array}
type Response<T> = {headers: {[key: string]: string}, buf: T};
type TextResponse = Response<string>;
type BufResponse = Response<Uint8Array>;
function fetch(url: string, options: FetchOptions = {}): Promise<BufResponse> {
const opts: any = parse(url);
@@ -28,7 +28,7 @@ function fetch(url: string, options: FetchOptions = {}): Promise<BufResponse> {
return new Promise((resolve, reject) => {
const req = request(opts, res => {
if (res.statusCode < 200 || res.statusCode >= 300) {
reject(res.statusCode);
reject(new Error(`HTTP Error: ${res.statusCode}`));
return;
}
@@ -54,13 +54,7 @@ function fetch(url: string, options: FetchOptions = {}): Promise<BufResponse> {
offset += size;
});
res.on('end', () => {
const headers = new Map();
if (opts.respHeaders) {
for (const header of opts.respHeaders) {
headers.set(header, res.headers[header]);
}
}
resolve({headers: headers, buf: Bytes.subarray(buf, 0, offset)});
resolve({headers: res.headers, buf: Bytes.subarray(buf, 0, offset)});
});
});
req.on('error', err => {
@@ -98,7 +92,7 @@ function normalizeBody(opts: FetchOptions): FetchOptions {
export function fetchText(url: string, options: FetchOptions = {}): Promise<TextResponse> {
return fetch(url, normalizeBody(options))
.then(({headers, buf}) => ({headers: headers, buf: bufferToString(buf)}));
.then(({headers, buf}) => ({headers, buf: bufferToString(buf)}));
}
export function fetchUint8Array(url: string, options: FetchOptions = {}): Promise<BufResponse> {

View File

@@ -11,12 +11,15 @@ import type {FetchOptions} from './fetch.js';
import type {ChunkStream} from './chunk-serializer.js';
import {serialize, deserializeChunks} from './chunk-serializer.js';
import {emptyChunk} from './chunk.js';
import {fetchUint8Array, fetchText} from './fetch.js';
import {
fetchUint8Array as fetchUint8ArrayWithoutVersion,
fetchText as fetchTextWithoutVersion,
} from './fetch.js';
import {notNull} from './assert.js';
import {NomsVersion} from './version.js';
import nomsVersion from './version.js';
const HTTP_STATUS_CONFLICT = 409;
const VersionHeader = 'X-Noms-Vers';
const versionHeader = 'x-noms-vers';
type RpcStrings = {
getRefs: string,
@@ -24,13 +27,20 @@ type RpcStrings = {
writeValue: string,
};
const versOptions = {
const versionOptions = {
headers: {
VersionHeader: NomsVersion,
[versionHeader]: nomsVersion,
},
respHeaders: [VersionHeader],
};
function fetchText(url: string, options: FetchOptions) {
return fetchTextWithoutVersion(url, mergeOptions(options, versionOptions));
}
function fetchUint8Array(url: string, options: FetchOptions) {
return fetchUint8ArrayWithoutVersion(url, mergeOptions(options, versionOptions));
}
const readBatchOptions = {
method: 'POST',
headers: {
@@ -81,8 +91,8 @@ export class Delegate {
constructor(rpc: RpcStrings, fetchOptions: FetchOptions) {
this._rpc = rpc;
this._rootOptions = mergeOptions(versOptions, fetchOptions);
this._readBatchOptions = mergeOptions(readBatchOptions, this._rootOptions);
this._rootOptions = fetchOptions;
this._readBatchOptions = mergeOptions(readBatchOptions, fetchOptions);
this._body = new ArrayBuffer(0);
}
@@ -150,10 +160,10 @@ export class Delegate {
}
function checkVersion(headers: {[key: string]: string}): ?Error {
const vers = headers[VersionHeader];
if (vers !== NomsVersion) {
const version = headers[versionHeader];
if (version !== nomsVersion) {
return new Error(
`SDK version ${NomsVersion} is not compatible with data of version ${vers}.`);
`SDK version ${nomsVersion} is not compatible with data of version ${version}.`);
}
return null;
}

View File

@@ -4,4 +4,4 @@
// Licensed under the Apache License, version 2.0:
// http://www.apache.org/licenses/LICENSE-2.0
export const NomsVersion = '1';
export default '1';

View File

@@ -5,7 +5,7 @@
"main": "dist/main.js",
"version": "1.0.1",
"dependencies": {
"@attic/noms": "^42.0.0",
"@attic/noms": "file:../../../js/",
"babel-regenerator-runtime": "6.5.0"
},
"devDependencies": {