I am trying out spray.io to write a basic REST endpoint that accepts a file from a client via multipart/form-data.
My route looks something like this:
val route = {
post {
path("upload") {
respondWithMediaType(`application/json`) {
entity(as[MultipartFormData]) {
formData => {
formField("file") {
file => {
val fileBodyPart: BodyPart = formData.get("file").get
val fileData: InputStream = new ByteArrayInputStream(fileBodyPart.entity.data.toByteArray)
// do something with the InputStream and respond
}
}
}
}
}
}
}
}
My concern is that the data.toByteArray call will read the whole file into memory. I'd like to read this directly as an InputStream instead so I can play with arbitrarily large files.
If I were using Jersey, I'd just use something like:
#FormDataParam("file") InputStream uploadedInputStream
as a parameter in the method that defines my REST resource. Is there an equivalent to this in Spray?
Thanks.
Related
I use kohttp to send requests to my service, but I got stuck.
Lets imagine that we have a function, that creates a request with some parameters that are represented as Map, and also append to them one or more additional param.
override fun request(urlString: String, params: Map<String, Any>?): Response {
val response = httpGet {
url = urlString
param {
"someName" to someValue
// also add 'params' map to request parameters
}
}
return response;
}
How can we do so?
I guess this should work:
param {
"someName" to someValue
params?.forEach { key, value ->
key to value
}
}
I am trying to upload a file from my angular code to an ASP.net backend.
My Angular code sends the object using FormData:
public uploadFiles(files) {
console.log(files);
if(files.length < 1) return;
const formData = new FormData();
files.forEach(file => {
console.log(file);
formData.append(file.name, file);
})
this._http.postFile('/order-processing/import-orders','application/x-www-form-urlencoded' ,formData).pipe(finalize(() => {
console.log("Finalized");
})).subscribe((val: any) => {
console.log('ORDER SUBMITTED', val);
}, error => {
console.log(error);
});
}
With the post file method looking like:
public postFile(path: string, contentType:string, body: FormData) : Observable<any> {
let headers = {
'Content-Type': contentType,
'Authorization': this.authToken
}
return this._http.post(environment.API_URL + path, body, {
headers
});
}
My ASP.net endpoint looks like:
[HttpPost, Route("hospitality/order-processing/import-orders")]
[RequestSizeLimit(2147483648)]
[DisableRequestSizeLimit]
public IActionResult UploadFile()
{
try
{
//var req = Request.Form.Files;
var file = Request.Form.Files;
string folderName = "Uploads";
string webRootPath = _hostingEnvironment.WebRootPath;
string newPath = Path.Combine(webRootPath, folderName);
if (!Directory.Exists(newPath))
{
Directory.CreateDirectory(newPath);
}
return Json("Upload Successful.");
}
catch (Exception e)
{
return Json("Failed:" + e);
}
}
If I check the network tab on my browser when I send the file, it says that my object is in the call, great, but for some reason it doesn't get picked up on the backend and when I step through the code it is not there.
I get different errors when I modify this code slightly. The error for the code in the state it is in now is "Form key or value length limit 2048 exceeded", however sometimes I get array out of bounds errors, or content boundary limit exceeded errors, it's enough to make you want to slam you face into your keyboard continually.
The whole point of this is to be able to upload an excel file to ASP.net code running in an AWS lambda, which then inserts rows in a RDS database. Am I going about this the right way? Is there a better way to achieve what I am trying to do? If not then what is wrong with my code that doesn't allow me to upload a file to a Web API?!
Thanks
It seems that you're trying to set the limit of the request but the message states that the problem is with form key or value length.
Try setting the RequestFormLimits and check if that helps.
[HttpPost, Route("hospitality/order-processing/import-orders")]
[RequestFormLimits(KeyLengthLimit = 8192, ValueLengthLimit = 8192)]
public IActionResult UploadFile()
I have code to use UnityWebRequest to call a webserver. Debugging it revealed that it never even calls the server and always returns with 0 bytes. I have attached a debugger to my server and can see that it does not get called.
Even tried calling google and still have an empty result. These tests were done from within the unity editor.
Is there something I need to enable before I can use networking libraries?
void Awake() {
StartCoroutine("CallWebserverCR");
}
IEnumerator CallWebserverCR()
{
UnityWebRequest www = UnityWebRequest.Get("http://www.google.com");
yield return www.Send();
if (www.isError)
{
Debug.Log(www.error);
}
else
{
Debug.Log(www.downloadHandler.text);
byte[] results = www.downloadHandler.data;
}
}
Only difference I see between your code and Unity's example is that they have a 'using' statement:
IEnumerator GetText() {
using(UnityWebRequest www = UnityWebRequest.Get("http://www.my-server.com")) {
yield return www.Send();
if(www.isError) {
Debug.Log(www.error);
}
else {
// Show results as text
Debug.Log(www.downloadHandler.text);
// Or retrieve results as binary data
byte[] results = www.downloadHandler.data;
}
}
}
I have a controller like this one:
#RestController
#RequestMapping('/v1/document')
class DocumentV1Controller {
#PutMapping
HttpEntity<Document> newdoc(
#RequestHeader Map<String, String> headers, #RequestParam('document') MultipartFile multipartFile) {
}
}
And I wan to test it using Spring MVC Test and Spock but I just can't figured out how to build a MockMultipartHttpServletRequestBuilder changing the HttpMethod from POST to PUT request.
This is the Spock specification:
class DocumentV1ControllerSpec extends BaseControllerSpec {
Should 'test and document good request on /v1/document endpoint'() {
given:
File file = new File('./src/test/resources/demoC.csv')
MockMultipartFile multipartFile = new MockMultipartFile('file',file.getBytes())
when:
ResultActions result = mockMvc.perform(fileUpload('/v1/document')
.file(multipartFile))
then:
result.andExpect(status().isCreated())
}
}
The error I get is this:
java.lang.AssertionError: Status expected:<201> but was:<405>
at org.springframework.test.util.AssertionErrors.fail(AssertionErrors.java:60)
at org.springframework.test.util.AssertionErrors.assertEquals(AssertionErrors.java:89)
at org.springframework.test.web.servlet.result.StatusResultMatchers$10.match(StatusResultMatchers.java:664)
at org.springframework.test.web.servlet.MockMvc$1.andExpect(MockMvc.java:171)
at gus.rest.api.v1.DocumentV1ControllerSpec.test and document good request on /v1/document endpoint(DocumentV1ControllerSpec.groovy:61)
What can I do to make it work?
I am not an expert in spock, however method fileUpload is deprecated now for Spring (at version 5.*).
There is a way to change default POST method for MockMultipartHttpServletRequestBuilder to PUT:
class DocumentV1ControllerSpec extends BaseControllerSpec {
Should 'test and document good request on /v1/document endpoint'() {
given:
File file = new File('./src/test/resources/demoC.csv')
MockMultipartFile multipartFile = new MockMultipartFile('file', file.getBytes())
MockMultipartHttpServletRequestBuilder multipart = (MockMultipartHttpServletRequestBuilder) multipart('/v1/document').with(request -> {
request.setMethod(HttpMethod.PUT);
return request;
});
when:
ResultActions result = mockMvc.perform(multipart
.file(multipartFile))
then:
result.andExpect(status().isCreated())
}
}
The trick is to use with(RequestPostProcessor postProcessor) to modify request and set method PUT to it.
public class Sampleontroller:apicontroller
{
public void PostBodyMethod() {
HttpRequestMessage request=this.request;
//How to read the multi part data in the method
}
}
I am sending a multi part data to webapi controller.
How to read the contents in the method?
An 'async' example:
public async Task<HttpResponseMessage> PostSurveys()
{
// Verify that this is an HTML Form file upload request
if (!Request.Content.IsMimeMultipartContent("form-data"))
{
return Request.CreateResponse(HttpStatusCode.BadRequest);
}
//Destination folder
string uploadFolder = "mydestinationfolder";
// Create a stream provider for setting up output streams that saves the output under -uploadFolder-
// If you want full control over how the stream is saved then derive from MultipartFormDataStreamProvider and override what you need.
MultipartFormDataStreamProvider streamProvider = new MultipartFormDataStreamProvider(uploadFolder );
MultipartFileStreamProvider multipartFileStreamProvider = await Request.Content.ReadAsMultipartAsync(streamProvider);
// Get the file names.
foreach (MultipartFileData file in streamProvider.FileData)
{
//Do something awesome with the files..
}
}
Have a look at the article by Mike Wasson:
http://www.asp.net/web-api/overview/working-with-http/sending-html-form-data,-part-2
Or if you are doing file uploads, here: www.strathweb.com/2012/08/a-guide-to-asynchronous-file-uploads-in-asp-net-web-api-rtm/