Skip to content

Commit

Permalink
8158 bug UI not using presign for uploading objects (#8365)
Browse files Browse the repository at this point in the history
  • Loading branch information
ItamarYuran authored Dec 10, 2024
1 parent 290ddef commit deb40d4
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 33 deletions.
32 changes: 26 additions & 6 deletions webui/src/lib/api/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,22 @@ export const defaultAPIHeaders = {
"X-Lakefs-Client": "lakefs-webui/__buildVersion",
};

export const parseRawHeaders = (rawHeaders) => {
const headersString = typeof rawHeaders === 'string' ? rawHeaders : rawHeaders.toString();
const cleanedHeadersString = headersString.trim();
const headerLines = cleanedHeadersString.split('\n');
const parsedHeaders = headerLines.reduce((acc, line) => {
let [key, ...value] = line.split(':'); // split into key and the rest of the value
key = key.trim();
value = value.join(':').trim();
if (key && value) {
acc[key.toLowerCase()] = value;
}
return acc;
}, {});
return parsedHeaders;
};

const authenticationError = "error authenticating request"

const apiRequest = async (uri, requestData = {}, additionalHeaders = {}) => {
Expand Down Expand Up @@ -670,12 +686,15 @@ export const uploadWithProgress = (url, file, method = 'POST', onProgress = null
resolve({
status: xhr.status,
body: xhr.responseText,
contentType: xhr.getResponseHeader('Content-Type'),
etag: xhr.getResponseHeader('ETag'),
contentMD5: xhr.getResponseHeader('Content-MD5'),
})
rawHeaders: xhr.getAllResponseHeaders(), // add raw headers
});
});
xhr.addEventListener('error', () => reject(new Error('Upload Failed')));
xhr.addEventListener('error', () => reject({
message: 'Upload Failed',
status: xhr.status,
body: xhr.responseText,
rawHeaders: xhr.getAllResponseHeaders(),
}));
xhr.addEventListener('abort', () => reject(new Error('Upload Aborted')));
xhr.open(method, url, true);
xhr.setRequestHeader('Accept', 'application/json');
Expand Down Expand Up @@ -749,8 +768,9 @@ class Objects {
async upload(repoId, branchId, path, fileObject, onProgressFn = null) {
const query = qs({path});
const uploadUrl = `${API_ENDPOINT}/repositories/${encodeURIComponent(repoId)}/branches/${encodeURIComponent(branchId)}/objects?` + query;
const {status, body, contentType} = await uploadWithProgress(uploadUrl, fileObject, 'POST', onProgressFn)
const {status, body, rawHeaders} = await uploadWithProgress(uploadUrl, fileObject, 'POST', onProgressFn)
if (status !== 201) {
const contentType = rawHeaders ? parseRawHeaders(rawHeaders)['content-type'] : undefined;
if (contentType === "application/json" && body) {
const responseData = JSON.parse(body)
throw new Error(responseData.message)
Expand Down
52 changes: 25 additions & 27 deletions webui/src/pages/repositories/repository/objects.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import Alert from "react-bootstrap/Alert";
import { BsCloudArrowUp } from "react-icons/bs";

import {humanSize, Tree} from "../../../lib/components/repository/tree";
import {objects, staging, retention, repositories, imports, NotFoundError, uploadWithProgress} from "../../../lib/api";
import {objects, staging, retention, repositories, imports, NotFoundError, uploadWithProgress, parseRawHeaders} from "../../../lib/api";
import {useAPI, useAPIWithPagination} from "../../../lib/hooks/api";
import {useRefs} from "../../../lib/hooks/repo";
import {useRouter} from "../../../lib/hooks/router";
Expand Down Expand Up @@ -226,42 +226,40 @@ const ImportModal = ({config, repoId, referenceId, referenceType, path = '', onD
);
};

function extractChecksumFromResponse(response) {
if (response.contentMD5) {
// convert base64 to hex
const raw = atob(response.contentMD5)
let result = '';
for (let i = 0; i < raw.length; i++) {
const hex = raw.charCodeAt(i).toString(16);
result += (hex.length === 2 ? hex : '0' + hex);
}
return result;
}

if (response.etag) {

function extractChecksumFromResponse(parsedHeaders) {
if (parsedHeaders['content-md5']) {
// drop any quote and space
return parsedHeaders['content-md5'];
}
// fallback to ETag
if (parsedHeaders['etag']) {
// drop any quote and space
return response.etag.replace(/[" ]+/g, "");
return parsedHeaders['etag'].replace(/[" ]+/g, "");
}
return ""
return null;
}

const uploadFile = async (config, repo, reference, path, file, onProgress) => {
const fpath = destinationPath(path, file);
const uploadFile = async (config, repo, reference, path, file, onProgress) => {
const fpath = destinationPath(path, file);
if (config.pre_sign_support_ui) {
let additionalHeaders;
if (config.blockstore_type === "azure") {
additionalHeaders = { "x-ms-blob-type": "BlockBlob" }
}
let additionalHeaders;
if (config.blockstore_type === "azure") {
additionalHeaders = { "x-ms-blob-type": "BlockBlob" }
}
const getResp = await staging.get(repo.id, reference.id, fpath, config.pre_sign_support_ui);
const uploadResponse = await uploadWithProgress(getResp.presigned_url, file, 'PUT', onProgress, additionalHeaders)
if (uploadResponse.status >= 400) {
throw new Error(`Error uploading file: HTTP ${status}`)
try {
const uploadResponse = await uploadWithProgress(getResp.presigned_url, file, 'PUT', onProgress, additionalHeaders);
const parsedHeaders = parseRawHeaders(uploadResponse.rawHeaders);
const checksum = extractChecksumFromResponse(parsedHeaders);
await staging.link(repo.id, reference.id, fpath, getResp, checksum, file.size, file.type);
} catch(error) {
throw new Error(`Error uploading file- HTTP ${error.status}${error.response ? `: ${error.response}` : ''}`);
}
const checksum = extractChecksumFromResponse(uploadResponse)
await staging.link(repo.id, reference.id, fpath, getResp, checksum, file.size, file.type);
} else {
await objects.upload(repo.id, reference.id, fpath, file, onProgress);
}
}
};

const destinationPath = (path, file) => {
Expand Down

0 comments on commit deb40d4

Please sign in to comment.