POST binary data from browser to JFrog / Artifactory server without using form-data - ajax

So we get a file (an image file) in the front-end like so:
//html
<input type="file" ng-change="onFileChange">
//javascript
$scope.onFileChange = function (e) {
e.preventDefault();
let file = e.target.files[0];
// I presume this is just a binary file
// I want to HTTP Post this file to a server
// without using form-data
};
What I want to know is - is there a way to POST this file to a server, without including the file as form-data? The problem is that the server I am send a HTTP POST request to, doesn't really know how to store form-data when it receives a request.
I believe this is the right way to do it, but I am not sure.
fetch('www.example.net', { // Your POST endpoint
method: 'POST',
headers: {
"Content-Type": "image/jpeg"
},
body: e.target.files[0] // the file
})
.then(
response => response.json() // if the response is a JSON object
)

You can directly attach the file to the request body. Artifactory doesn't support form uploads (and it doesn't look like they plan to)
You'll still need to proxy the request somehow to avoid CORS issues, and if you're using user credentials, you should be cautious in how you treat them. Also, you could use a library like http-proxy-middleware to avoid having to write/test/maintain the proxy logic.
<input id="file-upload" type="file" />
<script>
function upload(data) {
var file = document.getElementById('file-upload').files[0];
var xhr = new XMLHttpRequest();
xhr.open('PUT', 'https://example.com/artifactory-proxy-avoiding-cors');
xhr.send(file);
}
</script>

Our front-end could not HTTP POST directly to the JFrog/Artifactory server. So we ended up using a Node.js server as a proxy, which is not very ideal.
Front-end:
// in an AngularJS controller:
$scope.onAcqImageFileChange = function (e) {
e.preventDefault();
let file = e.target.files[0];
$scope.acqImageFile = file;
};
// in an AngularJS service
createNewAcqImage: function(options) {
let file = options.file;
return $http({
method: 'POST',
url: '/proxy/image',
data: file,
headers: {
'Content-Type': 'image/jpeg'
}
})
},
Back-end:
const express = require('express');
const router = express.Router();
router.post('/image', function (req, res, next) {
const filename = uuid.v4();
const proxy = http.request({
method: 'PUT',
hostname: 'engci-maven.nabisco.com',
path: `/artifactory/cdt-repo/folder/${filename}`,
headers: {
'Authorization': 'Basic ' + Buffer.from('cdt-deployer:foobar').toString('base64'),
}
}, function(resp){
resp.pipe(res).once('error', next);
});
req.pipe(proxy).once('error', next);
});
module.exports = router;
not that we had to use a PUT request to send an image to Artifactory, not POST, something to do with Artifactory (the engci-maven.nabisco.com server is an Artifactory server). As I recall, I got CORS issues when trying to post directly from our front-end to the other server, so we had to use our server as a proxy, which is something I'd rather avoid, but oh well for now.

Related

NativeScript Vue send request with form data (multipart/form-data)

I have a case in my application where I need to send data as form data to a server. The data includes a message and an optional list of files. The problem I'm facing is that when sending the request it's not being formed properly.
Request Payload
Expected (sample with the same request in the browser)
Actual (resulting request when running in NativeScript)
The actual result is that the payload is somehow being URL encoded.
Code example
sendData({ id, message, files }) {
const config = {
headers: {
'Content-Type': 'multipart/form-data'
}
};
const payload = new FormData();
payload.append('message', message);
if (files && files.length > 0) {
files.forEach((file) => {
payload.append(`files`, file, file.name);
});
}
return AXIOS_INSTANCE.post(
`/api/save/${id}`,
payload,
config
);
}
As you can see from the above, I'm using axios and also I'm trying to use FormData to format the data. From my research it seems that NativeScript used to not support binary data via XHR - however looking at this merge request on GitHub it looks like it's been fixed about a year ago.
So my suspicion is that I'm doing something wrong, maybe there's an alternative to using FormData, or else I shouldn't use axios for this particular request?
Version Numbers
nativescript 6.8.0
tns-android 6.5.3
tns-ios 6.5.3
Nativescript's background-http supports multipart form data.
See below for how its configured to do multipart upload
var bghttp = require("nativescript-background-http");
var session = bghttp.session("image-upload");
var request = {
url: url,
method: "POST",
headers: {
"Content-Type": "application/octet-stream"
},
description: "Uploading "
};
var params = [
{ name: "test", value: "value" },
{ name: "fileToUpload", filename: file, mimeType: "image/jpeg" }
];
var task = session.multipartUpload(params, request);

CORS Issue with AWS Lambda and Ajax

So I have been working on this serverless configuration that calls a Lambda function through ajax. The I enable CORs through the API Gateway, and I have made sure of the domain I specified. This domain works when calling other lambda functions within the same API.
Now for the weird stuff.
I send a post request (I am trying to upload a file through ajax, lambda, and S3), to my API. If I configure the Access-Control-Allow-Origin so that it points to the domain WITHOUT the http in front of it. Ex: example.com. When I try to call this i get:
Failed to load https://m562ogkc1l.execute-api.us-east-1.amazonaws.com/test/upload: Response to preflight request doesn't pass access control check: The 'Access-Control-Allow-Origin' header has a value 'example.com' that is not equal to the supplied origin. Origin 'http://example.com' is therefore not allowed access.
Ok fine, this is assumed, since that's not the proper domain. So when I add in the http (http://example.com) for the CORs of the API I get:
Failed to load https://m562ogkc1l.execute-api.us-east-1.amazonaws.com/test/upload: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://example.com' is therefore not allowed access. The response had HTTP status code 400.
What this seems like to me is that there is an issue elsewhere, except I don't know where the issue lies.
I have made sure the data I pass for parameters of the ajax call are strignified (JSON.stringify()), and I am NOT running an AWS Lambda Proxy which means I shouldn't be configuring responses on the lambda side of things.
This all really confuses me and I wish AWS had better documentation and examples since they really want to push these serverless services.
Further code is here:
Ajax:
$('#submitButton').on('click', function(){
//console.log(document.getElementById('fileUpload').value.substring(12));//C:\fakepath\ in front of filename (size = 12)
$.ajax({
type: 'POST',
url: 'https://m562ogkc1l.execute-api.us-east-1.amazonaws.com/test/upload',
data: JSON.stringify({"id": id,"name": document.getElementById('fileUpload').value.substring(12),"body": document.getElementById('fileUpload').files[0]}),
contentType: "application/json",
success: function(data){
console.log(data);
//location.reload();
}
});
return false;
});
Lambda:
const AWS = require('aws-sdk');
var s3 = new AWS.S3();
exports.handler = async (event) => {
let encodedImage = JSON.parse(event.body);
let decodedImage = Buffer.from(encodedImage, 'base64');
var filePath = event.id + '/' + event.name
var params = {
"Body": decodedImage,
"Bucket": "repository.example.com",
"Key": filePath
};
return await new Promise((resolve, reject) => {
s3.upload(params, function(err, data){
if(err) {
let response = {
"statusCode": 200,
"headers": {
"Access-Control-Allow-Origin": "http://example.com"
},
"body": JSON.stringify(err),
"isBase64Encoded": false
};
resolve(response);
} else {
let response = {
"statusCode": 200,
"headers": {
"Access-Control-Allow-Origin": "http://example.com"
},
"body": JSON.stringify(data),
"isBase64Encoded": false
};
resolve(response);
}
});
});
};
(Yes I threw in some response configuration for the function, I just wanted to see if it would work)

node.js express: How can I know if a request is an AJAX request?

Let's say I have a small piece of code:
var express = require('express');
var app = express();
app.get('/', function(req, res){
//I want to acccess 'req' and get info whether it's an AJAX call
});
app.listen(3000);
When I go inside the app.get(..) function, I want to know if the get request sent is an AJAX call. What is the field in the object 'req' that can tell me this?
The header X-Requested-With: XMLHttpRequest HTTP header is not automatically added to an AJAX request, either with fetch or with the old-fashioned use of the XMLHttpRequest object. It is often added by client libraries such as jQuery.
If the header is present, it is represented in Express by request.xhr.
If you want to add it to the request (the simplest solution to this problem) you can add it as a custom header with fetch:
fetch(url, {
headers: {
"X-Requested-With": "XMLHttpRequest"
}
});
This will now be reflected in req.xhr.
A better solution is to set the Accept header to a sensible value. If you want JSON to be returned, set Accept to application/json:
fetch(url, {
headers: {
"Accept": "application/json"
}
});
You can then test for this with req.accepts:
switch (req.accepts(['html', 'json'])) { //possible response types, in order of preference
case 'html':
// respond with HTML
break;
case 'json':
// respond with JSON
break;
default:
// if the application requested something we can't support
res.status(400).send('Bad Request');
return;
}
This is much more powerful than the req.xhr approach.
app.get('/', function(req, res){
//I want to acccess 'req' and get info whether it's an AJAX call
if(req.xhr){
//the request is ajax call
}
})
var express = require('express');
var app = express();
app.get('/', function(req, res){
var isAjax = req.xhr;
});
app.listen(3000);

How to send data from server to client via http?

I want to send the filepath of a file on my server to the client in order to play it using a media player. How can I retrieve that string on the client side in order to concatenate it in the src attribute of a <video element without using sockets?
Server snippet:
res.set('content-type', 'text/plain');
res.send('/files/download.mp4');
This is how you make a request to the server without any frameworks. "/path_to_page" is the route you set to the page that is supposed to process the request.
var xhr = new XMLHttpRequest();
xhr.open('GET', '/path_to_page', true);
xhr.onload = function(e) {
if (this.status == 200) {
console.log(this.responseText); // output will be "/files/download.mp4"
}
};
xhr.send();
}
You might also want to send some params.
var formdata = new FormData();
formdata.append("param_name", "value");
So you might for instance want to send the filename or such.
You just need to change 2 lines from the first code snippet. One would be
xhr.open('POST', '/path_to_page', true); // set to post to send the params
xhr.send(formdata); // send the params
To get the params on the server, if you are using express, they are in req.body.param_name
Which framework are you using??
You can declare base path of your project directory in ajax and the followed by your file.
jQuery.ajax({
type: "GET",
url: "/files/download.mp4",
});
Since you are using express (on node), you could use socket.io:
Server:
var io = require('socket.io').listen(80),
fs = require('fs');
io.sockets.on('connection', function (socket) {
socket.on('download', function(req) {
fs.readFile(req.path, function (err, data) {
if (err) throw err;
socket.emit('video', { video: data });
});
});
});
Client:
<script src="/socket.io/socket.io.js"></script>
<script>
var socket = io.connect('http://localhost');
...
// request a download
socket.emit('download', { path: '/files/download.mp4' });
// receive a download
socket.on('video', function (data) {
// do sth with data.video;
});
...
</script>
Edit: didnt notice you didnt want to use sockets. Still it is a viable solution.

How can I send an AJAX request to a node.js server via HTTPS?

I have the following node.js server set up listening to port 9001
var https = require('https');
var fs = require('fs');
var qs = require('querystring');
var options = {
key: fs.readFileSync('privatekey.pem'),
cert: fs.readFileSync('certificate.pem')
};
https.createServer(options, function (req, res) {
res.writeHead(200);
console.log('Request Received!');
console.log(req.method);
if (true || req.method == 'POST') {
var body = '';
req.on('data', function (data) {
body += data;
});
req.on('end', function () {
console.log(body);
var POST = qs.parse(body);
console.log(POST);
});
}
res.end("hello, world\n");
}).listen(9001);
and I am trying to get this server to respond to an AJAX call
function form_save()
{
console.log("submitted!");
var data_obj = {
data1: "item1",
data2: "item2"
}
$.ajax({
url: 'https://adam.testserver.com:9001/',
type: "POST",
dataType: "json",
data: data_obj,
success: function() {
console.log("success!");
},
complete: function() {
console.log("complete!");
}
});
}
There are two problems occurring with my arrangement. The first is that if I start the server and then click the button that triggers my form_save() the node server does not respond and I get the following error:
submitted!
OPTIONS https://adam.testserver.com:9001/ Resource failed to load
jQuery.extend.ajaxjquery.js:3633
$.ajaxjquery.validate.js:1087
form_savew_worksheet.php:64
confirm_deletew_worksheet.php:95
jQuery.event.handlejquery.js:2693
jQuery.event.add.handlejquery.js:2468
w_worksheet.php:73
complete!
At this point if I access that url directy (https://adam.testserver.com:9001/) I get the expected "hello, world" output as well as the console message "Request Received!
GET". From this point on if I click the button to trigger my AJAX call I get a new error.
submitted!
XMLHttpRequest cannot load https://adam.testserver.com:9001/. Origin
https://adam.testserver.com is not allowed by Access-Control-Allow-Origin.
w_worksheet.php:73
complete!
I don't understand why I get this message as both my form and node server reside on the same server. Thanks for taking the time to read, I appreciate any help I can get on this. I've been stuck for a while now!
You've run into the Cross-Origin Resource Sharing (CORS) specification.
Note the OPTIONS in your output. The OPTIONS HTTP Verb is used by the browser to query the web server about the URL, not to GET its contents or POST data to it.
Your server doesn't respond with the correct header data on a CORS request, so your browser assumes it has no rights to access the data, and refuses to GET or POST to the URL.
If you truly want to let any website in the world run that AJAX request, you can do something similar to the following:
function handleOptions(request, response) {
response.writeHead(200, {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Method": "POST, GET, OPTIONS",
"Access-Control-Allow-Headers": request.headers["access-control-request-headers"]
});
response.end();
}
function server(request, response) {
if(request.method == "POST") {
handlePost(request, response);
} else if(request.method == "OPTIONS") {
handleOptions(request, response);
} else {
handleOther(response);
}
}
https.createServer(sslObj, server).listen(9001);
You can fill in the details and whether you should handle GET separately, and so on (handleOther should return an appropriate error code for each request method you don't support).

Resources