Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

8158 bug UI not using presign for uploading objects #8365

Merged
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;
};

Comment on lines +58 to +73
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

moved this func here, as ive seen that no func was exported from the other file to this, and it is now also being used in this file.

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'),
Jonathan-Rosenberg marked this conversation as resolved.
Show resolved Hide resolved
etag: xhr.getResponseHeader('ETag'),
contentMD5: xhr.getResponseHeader('Content-MD5'),
Jonathan-Rosenberg marked this conversation as resolved.
Show resolved Hide resolved
})
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'];
Jonathan-Rosenberg marked this conversation as resolved.
Show resolved Hide resolved
}
// 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
Loading