mirror of
https://github.com/dolthub/dolt.git
synced 2026-02-09 03:09:12 -06:00
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:
@@ -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.
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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": {
|
||||
|
||||
Reference in New Issue
Block a user