SpringBoot cannot parse MultipartFile properly - spring

Hello and thank you for your time in advance.
I have really simple code below for downloading/uploading files:
This is frontend:
function upload() {
let data = new FormData();
data.append("file", refs.inputFile.current.files[0]);
let config = {
headers: {
"Content-Type" : "multipart/form-data"
}
};
console.log(data);
axios.post("http://localhost:8080/upload",
data)
.then((res) => {
console.log(res.data);
})
.catch((err) => {
console.log(err.message);
});
}
When i open request in Firefox i can see file content just fine, content in files is just fine on this side. But backend returns to me totally different thing.
So this is backend code:
Controller:
#PostMapping(path = "/upload")
public String upload(
#RequestParam("file") MultipartFile file) throws IOException {
if(repo.existsByFilename(file.getOriginalFilename())) {
return "File already exists.";
} else {
Model m = new Model(
file.getOriginalFilename(),
file.getBytes()
);
repo.save(m);
return "File named " + file.getOriginalFilename() + " is saved.";
}
Model entity:
#Entity
#Table(name = "test")
public class Model {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String filename;
#Lob
#Column(columnDefinition = "longblob")
private byte[] data;
public Model() {
super();
}
public Model(String filename, byte[] data) {
super();
this.filename = filename;
this.data = data;
}
public Model(Long id, String filename, byte[] data) {
super();
this.id = id;
this.filename = filename;
this.data = data;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getFilename() {
return filename;
}
public void setFilename(String filename) {
this.filename = filename;
}
public byte[] getData() {
return data;
}
public void setData(byte[] data) {
this.data = data;
}
}
I when i try to upload a simple text file named testtext.txt with "TESTTEXT" i can open request in firefox and i get this:
-----------------------------3431304631829148335501350923
Content-Disposition: form-data; name="file"; filename="testtext.txt"
Content-Type: text/plain
TESTTEXT
-----------------------------3431304631829148335501350923--
BUT when i access MySQL DB directly i have this content:
0x54455354544558540A
When you convert it to ASCII it doesn't make sense. Its the same when i try this code with images also. It always returns me more than i send it.
For almost two days i cannot get simple file up/down working and i don't understand where am i making mistake. This is just minimal and straightforward code, it should work...
Download function:
function download(filename) {
console.log(filename);
axios({
url: "http://localhost:8080/api/dataupdown/download",
method: "GET",
params: {
filename: filename
}
}).then((res) => {
const url = window.URL.createObjectURL(new Blob([res.data.data]));
const link = document.createElement("a");
link.href = url;
link.setAttribute("download", res.data.filename);
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
});
}

Your lob is encoded in hexadecimal. if you convert it to string, you will find the result you are looking for 'TESTTEST' :
function hex2a(hexx) {
var hex = hexx.toString();//force conversion
var str = '';
for (var i = 0; i < hex.length; i += 2)
str += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
return str;
}
console.log(hex2a('54455354544558540A')); // returns 'TESTTEST'
EDIT: response to second question added by edit about the download
You exposed the controller data encoded in base64. If you decoded, you will find back 'TESTTEXT'
var decoded = atob("VEVTVFRFWFQK");
console.log(decoded); //returns 'TESTTEXT'

Thank you very much Thomas for helping me. My problem is completely solved. I can now upload all files(images, videos, text, documents) and i can download them just exactly as they are originally.
So apparently after following tons of tutorials no one mentioned even once that data was encoded in Base64 scheme, and to be honest i didn't know anything about Base64 before... I used js-base64 lib because it has nice prebuilt stuff that should be(in my opinion) base in JS.
Summary:
Data you send to SQL is raw HEX/BINARY code.
When you GET/POST data from SQL, browser(or JS?) converts it to Base64 scheme.
After getting data you need to convert it raw binary format.
Convert Base64 data to Uint8Array if you are working with media,documents or anything else literally. Plain text files(ASCII) are fine without it.
So here is updated download code(i use node-js but there is js-base64 for plain JS also):
import {Base64} from "js-base64";
function download(filename) {
axios({
url: "http://192.168.0.149:8080/api/dataupdown/download",
method: "GET",
params: {
filename: filename
},
})
.then((res) => {
console.log(res.data);
const url = window.URL.createObjectURL(new Blob([Base64.toUint8Array(res.data.data)]));
const link = document.createElement("a");
link.href = url;
link.setAttribute("download", res.data.filename);
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
});
}

Related

One additional duplicate file gets uploaded

I have my FileUpload Controller like this:
#PostMapping("/uploadFile")
public AppUserDocumentUploadResponse uploadFile(#RequestParam("file") MultipartFile file) {
AppUserDocument dbFile = appUserDocumentStorageService.storeFile(file);
String fileDownloadUri = ServletUriComponentsBuilder.fromCurrentContextPath()
.path("/downloadFile/")
.path(dbFile.getId().toString())
.toUriString();
return new AppUserDocumentUploadResponse(dbFile.getDbfileName(), fileDownloadUri,
file.getContentType(), file.getSize());
}
#PostMapping("/uploadMultipleFiles")
public List<AppUserDocumentUploadResponse> uploadMultipleFiles(#RequestParam("files") MultipartFile[] files) {
return Arrays.asList(files)
.stream()
//.peek(fileBeingProcessed -> log.info("Processing File (PEEK2): {} ", fileBeingProcessed))
.map(file -> uploadFile(file))
.collect(Collectors.toList());
}
My Service is:
public AppUserDocument storeFile(MultipartFile file) {
String fileName = StringUtils.cleanPath(file.getOriginalFilename());
try {
if (fileName.contains("..")) {
throw new FileStorageException("Sorry! Filename contains invalid path sequence " + fileName);
}
AppUserDocument dbFile = new AppUserDocument(UUID.randomUUID().toString(), fileName, file.getContentType(),
file.getSize(), file.getBytes());
return dbFileRepository.save(dbFile);
} catch (IOException ex) {
throw new FileStorageException("Could not store file " + fileName + ". Please try again!", ex);
}
}
Model goes like this:
#Data
#NoArgsConstructor
#AllArgsConstructor
#Entity(name = "AppDBFiles")
#Table(name = "app_db_files")
public class AppUserDocument extends Auditable<String> {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "dbfileuuid", unique = true)
#GenericGenerator(name = "uuid", strategy = "uuid4")
private String dbfileuuid;
#Column(name = "dbfilename")
private String dbfileName;
#Column(name = "dbfiletype")
private String dbfileType;
#Column(name = "dbfilesize")
private long dbfileSize;
#Lob
#Column(name = "dbfiledata")
private byte[] dbfileData;
#Version
#Column(name="optlock")
private Integer version;
public AppUserDocument(String dbfileName, String dbfileType, long dbfileSize, byte[] dbfileData) {
super();
this.dbfileName = dbfileName;
this.dbfileType = dbfileType;
this.dbfileSize = dbfileSize;
this.dbfileData = dbfileData;
}
public AppUserDocument(String dbfileuuid, String dbfileName, String dbfileType, long dbfileSize, byte[] dbfileData) {
super();
this.dbfileName = dbfileName;
this.dbfileType = dbfileType;
this.dbfileSize = dbfileSize;
this.dbfileData = dbfileData;
}
}
And my (vanilla) JS goes like this:
function uploadMultipleFiles(files) {
var formData = new FormData();
for (var index = 0; index < files.length; index++) {
formData.append("files", files[index]);
}
const fileField = document.querySelector('input[type="file"]');
//formData.append('filecusomName', 'abc123');
formData.append('files', fileField.files[0]);
fetch('/uploadMultipleFiles', {
method: 'POST',
body: formData
})
.then((response) => response.json())
.then((response) => {
console.log('Success:', response);
multipleFileUploadError.style.display = "none";
var content = "<p>All Files Uploaded Successfully</p>";
document.getElementById("multipleFileUploadInput").value = null;
for (var i = 0; i < response.length; i++) {
content += "<p>DownloadUrl : <a href='" + response[i].fileDownloadUri + "' target='_blank'>" + response[i].fileDownloadUri + "</a></p>";
}
multipleFileUploadSuccess.innerHTML = content;
multipleFileUploadSuccess.style.display = "block";
})
.catch((error) => {
multipleFileUploadSuccess.style.display = "none";
multipleFileUploadError.innerHTML = (response && response.message) || "Some Error Occurred";
console.error('Error:', error);
});
}
All goes well except one additional file gets uploaded which is duplicate of the first file.
This is what my console logs show:
Success:
(3) [{…}, {…}, {…}]
0
:
fileDownloadUri
:
"http://localhost:8080/downloadFile/39"
fileName
:
"sample.pdf"
fileType
:
"application/pdf"
size
:
357896
[[Prototype]]
:
Object
1
:
fileDownloadUri
:
"http://localhost:8080/downloadFile/40"
fileName
:
"sample2.pdf"
fileType
:
"application/pdf"
size
:
357896
[[Prototype]]
:
Object
2
:
fileDownloadUri
:
"http://localhost:8080/downloadFile/41"
fileName
:
"sample.pdf"
fileType
:
"application/pdf"
size
:
357896
[[Prototype]]
:
Object
length
:
3
And my DB looks like this:
Uncommenting the .peek(fileBein in my controller also shows this (twice for the duplicate file):
2022-12-07T00:23:25.971-05:00 INFO 11344 --- [nio-8080-exec-2] c.c.s.c.AppUserDocumentRestController : Processing File
What I am doing wrong here? Also not sure where to debug this situation. Any pointers/directions will be greatly appreciated.
Please once go through your JS file:
function uploadMultipleFiles(files) {
var formData = new FormData();
for (var index = 0; index < files.length; index++) {
formData.append("files", files[index]);
}
const fileField = document.querySelector('input[type="file"]');
//formData.append('filecusomName', 'abc123');
formData.append('files', fileField.files[0]);
fetch('/uploadMultipleFiles', {
method: 'POST',
body: formData
})
...
...
}
The 9th line formData.append('files', fileField.files[0]); is adding duplicate of First file in FormData.

i cant export my list to csv using mvc c#

I have been dealing with a problem for hours,kindly help me,the following is my ajax which post data to controller :
$.ajax({
dataType: "json",
type: "POST",
url: "#Url.Action("CreateCSVFile ","Turbine ")",
contentType: "application/json;charset=utf-8",
data: JSON.stringify(data),
success: function(result) {}
})
It posts the result I want to controller,but problem starts from here that in my controller after making the export file,i don't see any thing in the browser to save it,i have no idea where is going wrong,the following is my controller:
public FileContentResult CreateCSVFile(string turbinename, string frm_date, string to_date)
{
var eventResult = (from c in DB.Events
where (c.m_turbine_id == turbineid.turbineID) && (c.m_time_stamp >= frmDate && c.m_time_stamp <= toDate)
select new EventLogPartialViewModel
{
Timestamp = c.m_time_stamp,
Description = c.m_event_log_description,
WindSpeed = c.m_wind_speed,
RPM = c.m_rpm,
Power = c.m_power
}).ToList().Select(x => new
{
Timestamp = x.Timestamp.ToString("dd/MM/yyyy H:mm:ss"),
Description = x.Description,
WindSpeed = x.WindSpeed,
RPM = x.RPM,
Power = x.Power
}).ToList().OrderByDescending(m => m.Timestamp);
var st = DataTag.NoExclusion;
string csv = "Charlie, Chaplin, Chuckles";
byte[] csvBytes = ASCIIEncoding.ASCII.GetBytes(CSVExport.GetCsv(eventResult.ToList(), st));
return File(new System.Text.UTF8Encoding().GetBytes(csv), "text/csv", "Report123.csv");
}
To fit your particular function, you can do :
public FileContentResult CreateCSVFile()
{
string csv = "Charlie, Chaplin, Chuckles";
byte[] csvBytes = Encoding.UTF8.GetBytes(csv); // or get your bytes the way you want
string contentType = "text/csv";
var result = new FileContentResult(csvBytes, contentType);
return result;
}
This is how you can create a FileContentResult.
However, I prefer to use this to send a response with a file :
[HttpGet]
public HttpResponseMessage GetYourFile()
{
string csv = "Charlie, Chaplin, Chuckles";
byte[] csvBytes = Encoding.UTF8.GetBytes(csv); // or get your bytes the way you want
var result = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new ByteArrayContent(csvBytes);
};
result.Content.Headers.ContentDisposition =
new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment")
{
FileName = "YourFileName.csv"
};
result.Content.Headers.ContentType =
new MediaTypeHeaderValue("text/csv");
return result;
}
(adapted from How to return a file (FileContentResult) in ASP.NET WebAPI).
Extracting the "csv" part in bytes is another problem completely, I hope your problem was about the "create a file" part.

How to upload picture in Spring and Angular2

I want to upload picture in my app this is my Angular 2 code
constructor () {
this.progress = Observable.create(observer => {
this.progressObserver = observer
}).share();
}
public makeFileRequest (url: string, params: string[], files: File[]):
Observable<any> {
return Observable.create(observer => {
let formData: FormData = new FormData(),
xhr: XMLHttpRequest = new XMLHttpRequest();
for (let i = 0; i < files.length; i++) {
formData.append("uploads[]", files[i], files[i].name);
}
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
observer.next(JSON.parse(xhr.response));
observer.complete();
} else {
observer.error(xhr.response);
}
}
};
xhr.upload.onprogress = (event) => {
this.progress = Math.round(event.loaded / event.total * 100);
this.progressObserver.next(this.progress);
};
xhr.open('POST', url, true);
xhr.send(formData);
});
}
And this is my spring controller
#RequestMapping(value = "/upload", method = RequestMethod.POST)
#ResponseBody
public ResponseEntity<?> uploadFile(
#RequestParam("file") MultipartFile uploadfile) {
try {
// Get the filename and build the local file path (be sure that the
// application have write permissions on such directory)
String filename = uploadfile.getOriginalFilename();
String directory = "/assets/images";
String filepath = Paths.get(directory, filename).toString();
// Save the file locally
BufferedOutputStream stream =
new BufferedOutputStream(new FileOutputStream(new File(filepath)));
stream.write(uploadfile.getBytes());
stream.close();
}
catch (Exception e) {
System.out.println(e.getMessage());
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
}
return new ResponseEntity<>(HttpStatus.OK);
}
And I get this error
ERROR {"timestamp":1498220487868,"status":400,"error":"Bad Request","exception":"org.springframework.web.multipart.support.MissingServletRequestPartException","message":"Required request part 'file' is not present","path":"/webapp/api/picture/upload"}
You specify #RequestParam("file"), which means Spring waits for an argument with the key file, but you append your data with the key uploads.
Also, as said by #ledniov, your are receiving an array of multipart files, not only one.
Corrections if you want to use the key file:
// Front-End: change "uploads" to "file"
formData.append("file[]", files[i], files[i].name);
// Back-End: takes a MultipartFile array
#RequestParam("file") MultipartFile[] uploadfile
Corrections if you want to use the key uploads:
// Back-End: change "file" to "uploads" and takes a MultipartFile array
#RequestParam("uploads") MultipartFile[] uploadfile

send data from ajax to spring controller

var form_data = {
itemid: globalSourceItem.substr(globalSourceItem.indexOf("-") + 1),
columnName: jqInputs[0].value,
displayName: jqInputs[1].value,
format: jqInputs[2].value,
KBE: jqInputs[3].value,
dgroup: jqInputs[4].value,
dupkey: jqInputs[5].value ,
measurement: jqInputs[6].value ,
times: new Date().getTime()
};
// console.log(form_data);
// console.log($("#tourl").html());
$.ajax({
url: $("#tourl").html(),
type: 'POST',
datatype: 'json',
data: form_data,
success: function(message) {
var j_obj = $.parseJSON(message);
// console.log(j_obj);return false;
if (j_obj.hasOwnProperty('success')) {
toastr.info('Item updated successfully');
setTimeout(function(){
window.location.reload();
},1000);
} else {
toastr.info('There was a problem.');
}
},
error: function(xhr, textStatus, errorThrown)
{
toastr.info('There seems to be a network problem. Please try again in some time.');
}
});
}
Hii friends , this code is working for php and i need to send the same data to the spring mvc through the ajax , can anyone please help me with the exact solution where to make changes as Iam struckup with the same doubt for like 2 weeks...
public class TestController {
#RequestMapping(value = "url", method = RequestMethod.POST)
public ModelAndView action(#RequestBody FormData formData) {
...
}
}
public class FormData {
private String itemid;
public String getItemid() {
return itemid;
}
public void setItemid(String itemid) {
this.itemid = itemid;
}
//...
}
Try sth like this. You should be able to map JSON Object to Java Object.
Maybe you could use annotation #ResponseBody and convert JSONObject to String:
#RequestMapping(value = "/ajax", method = RequestMethod.POST, produces="application/json")
#ResponseBody
public String ajax(#RequestBody ListDataDefinition listDataDefinition) {
System.out.println("id="+listDataDefinition.getItemid());
int i=SchemaDAOI.updateldd(listDataDefinition);
String message="success";
JSONObject obj = new JSONObject();
try {
obj.put("success", "success");
}
catch (JSONException e) {
e.printStackTrace();
}
if(i==1){
System.out.println("success");
}
else{
System.out.println("failure");
}
return obj.toString();
}
}
If you send String to View as ResponseBody and set produces as JSON it should be treated as pure JSON RQ.

How to send pre request before every request in windows phone 7

I want to send one pre request to my server before send every request. From that pre request I will receive the token from my server and than I have to add that token into all the request. This is the process.
I have try with some methods to achieve this. But I am facing one problem. That is, When I try to send pre request, it is processing with the current request. That mean both request going parallel.
I want to send pre request first and parse the response. After parsing the pre request response only I want to send that another request. But first request not waiting for the pre request response. Please let me any way to send pre request before all the request.
This is my code:
ViewModel:
`public class ListExampleViewModel
{
SecurityToken sToken = null;
public ListExampleViewModel()
{
GlobalConstants.isGetToken = true;
var listResults = REQ_RESP.postAndGetResponse((new ListService().GetList("xx","xxx")));
listResults.Subscribe(x =>
{
Console.WriteLine("\n\n..................................2");
Console.WriteLine("Received Response==>" + x);
});
}
}`
Constant Class for Request and Response:
`public class REQ_RESP
{
private static string receivedAction = "";
private static string receivedPostDate = "";
public static IObservable<string> postAndGetResponse(String postData)
{
if (GlobalConstants.isGetToken)
{
//Pre Request for every reusest
receivedPostDate = postData;
GlobalConstants.isGetToken = false;
getServerTokenMethod();
postData = receivedPostDate;
}
HttpWebRequest serviceRequest =
(HttpWebRequest)WebRequest.Create(new Uri(Constants.SERVICE_URI));
var fetchRequestStream =
Observable.FromAsyncPattern<Stream>(serviceRequest.BeginGetRequestStream,
serviceRequest.EndGetRequestStream);
var fetchResponse =
Observable.FromAsyncPattern<WebResponse>(serviceRequest.BeginGetResponse,
serviceRequest.EndGetResponse);
Func<Stream, IObservable<HttpWebResponse>> postDataAndFetchResponse = st =>
{
using (var writer = new StreamWriter(st) as StreamWriter)
{
writer.Write(postData);
writer.Close();
}
return fetchResponse().Select(rp => (HttpWebResponse)rp);
};
Func<HttpWebResponse, IObservable<string>> fetchResult = rp =>
{
if (rp.StatusCode == HttpStatusCode.OK)
{
using (var reader = new StreamReader(rp.GetResponseStream()))
{
string result = reader.ReadToEnd();
reader.Close();
rp.GetResponseStream().Close();
XDocument xdoc = XDocument.Parse(result);
Console.WriteLine(xdoc);
return Observable.Return<string>(result);
}
}
else
{
var msg = "HttpStatusCode == " + rp.StatusCode.ToString();
var ex = new System.Net.WebException(msg,
WebExceptionStatus.ReceiveFailure);
return Observable.Throw<string>(ex);
}
};
return
from st in fetchRequestStream()
from rp in postDataAndFetchResponse(st)
from str in fetchResult(rp)
select str;
}
public static void getServerTokenMethod()
{
SecurityToken token = new SecurityToken();
var getTokenResults = REQ_RESP.postAndGetResponse((new ServerToken().GetServerToken()));
getTokenResults.Subscribe(x =>
{
ServerToken serverToken = new ServerToken();
ServiceModel sm = new ServiceModel();
//Parsing Response
serverToken = extract(x, sm);
if (!(string.IsNullOrEmpty(sm.NetErrorCode)))
{
MessageBox.Show("Show Error Message");
}
else
{
Console.WriteLine("\n\n..................................1");
Console.WriteLine("\n\nserverToken.token==>" + serverToken.token);
Console.WriteLine("\n\nserverToken.pk==>" + serverToken.pk);
}
},
ex =>
{
MessageBox.Show("Exception = " + ex.Message);
},
() =>
{
Console.WriteLine("End of Process.. Releaseing all Resources used.");
});
}
}`
Here's couple options:
You could replace the Reactive Extensions model with a simpler async/await -model for the web requests using HttpClient (you also need Microsoft.Bcl.Async in WP7). With HttpClient your code would end up looking like this:
Request and Response:
public static async Task<string> postAndGetResponse(String postData)
{
if (GlobalConstants.isGetToken)
{
//Pre Request for every reusest
await getServerTokenMethod();
}
var client = new HttpClient();
var postMessage = new HttpRequestMessage(HttpMethod.Post, new Uri(Constants.SERVICE_URI));
var postResult = await client.SendAsync(postMessage);
var stringResult = await postResult.Content.ReadAsStringAsync();
return stringResult;
}
Viewmodel:
public class ListExampleViewModel
{
SecurityToken sToken = null;
public ListExampleViewModel()
{
GetData();
}
public async void GetData()
{
GlobalConstants.isGetToken = true;
var listResults = await REQ_RESP.postAndGetResponse("postData");
}
}
Another option is, if you want to continue using Reactive Extensions, to look at RX's Concat-method. With it you could chain the token request and the actual web request: https://stackoverflow.com/a/6754558/66988

Resources