Posting multipart form data to seam+RESTeasy fails marshalling to InputStream - post

I'm trying to post image data to a seam+RESTeasy endpoint and I'm getting a very cryptic error during JBoss startup. The HTTP request I'm sending has a content-type of multipart/form-data which has a single image/jpeg part with name "attachment". My service method looks like this:
#POST
#Path("uploadSymptomsImage/{appointmentGUID}")
#Consumes(MediaType.MULTIPART_FORM_DATA)
#Produces("application/json")
public String uploadSymptomsImage( #FormParam("attachment") InputStream fileInputStream,
#PathParam("appointmentGUID") String strAppointmentGUID )
{ ...
The error that I get is during startup:
Caused by: java.lang.RuntimeException: Unable to find a constructor that takes a String param or a valueOf() or fromString() method for javax.ws.rs.FormParam("attachment") on public java.lang.String com....AppointmentRestService.uploadSymptomsImage(java.io.InputStream,java.lang.String) for basetype: java.io.InputStream
at org.jboss.resteasy.core.StringParameterInjector.initialize(StringParameterInjector.java:206) [:]
at org.jboss.resteasy.core.StringParameterInjector.<init>(StringParameterInjector.java:57) [:]
at org.jboss.resteasy.core.FormParamInjector.<init>(FormParamInjector.java:22) [:]
My understanding was that media types could be automatically marshalled to InputStream. I've also tried java.io.File, java.io.Reader - both with same error. When I replace with byte[] or String I get a zero length array, or null as the parameter value.
How would you go about debugging this? Also, is it possible to access the raw request or pre-marshalled values?
Any suggestions here would be greatly appreciated.

You should retrieve the contents using MultipartFormDataInput. See the following example:
#POST
#Path("uploadSymptomsImage/{appointmentGUID}")
#Consumes(MediaType.MULTIPART_FORM_DATA)
#Produces("application/json")
public String uploadSymptomsImage(#PathParam("appointmentGUID") String strAppointmentGUID,
MultipartFormDataInput formData) {
Map<String, List<InputPart>> formDataMap = formData.getFormDataMap();
List<InputPart> attachments = formDataMap.get("attachment");
for(InputPart attachment : attachments) {
String fileName = extractFilename(attachment);
if(fileName.isEmpty()) continue;
InputStream in = attachment.getBody(new GenericType<InputStream>() {});
// Interact with stream
}
// Respond
}
The extractFilename method is a helper method I wrote:
private static String extractFilename(final InputPart attachment) {
Preconditions.checkNotNull(attachment);
MultivaluedMap<String, String> headers = attachment.getHeaders();
String contentDispositionHeader = headers.getFirst("Content-Disposition");
Preconditions.checkNotNull(contentDispositionHeader);
for(String headerPart : contentDispositionHeader.split(";(\\s)+")) {
String[] split = headerPart.split("=");
if(split.length == 2 && split[0].equalsIgnoreCase("filename")) {
return split[1].replace("\"", "");
}
}
return null;
}

Related

Passing body in restassured using Generics doesn't work for File and FileInputStream

In Restassured we can pass the request payload in the body method by different ways like
String
POJO Object
Map Object
JsonObject (from GSON library)
File and
FileInputStream
So, I created following one method using generics to accommodate all these types: -
public <T> Response postAMember(T body) {
return given().spec(this.spec).body(body).when().post(EndPoints.GET_ALL_POST_A_MEMBER).andReturn();
}
Now, this is how I'm consuming it for respective Type (Not all in one go...one at a time): -
#Test
public void postMember() throws IOException {
// Using Hashmap
Map<String, String> body = new HashMap<>();
body.put("name", "Rocky");
body.put("gender", "Male");
// Using Model and GSON
Member imember = new Member("Rocky", "Male");
Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
String body = gson.toJson(imember);
// Using JsonObject (GSON)
JsonObject body = new JsonObject();
body.addProperty("name", "Rocky");
body.addProperty("gender", "Male");
// Using Payload JSON File
File body = new File("src/test/resources/Payloads/postmemberpayload.json");
// Using Raw String
String body = "{\r\n" +
" \"name\": \"Rocky\",\r\n" +
" \"gender\": \"Male\"\r\n" +
"}";
// Using FileInputStream
FileInputStream fis = new FileInputStream(body); // in this case I would pass fis to body method
Response resp = MemberService.getMemberServiceInstance().postAMember(body);
Assert.assertEquals(resp.getStatusCode(), StatusCode.CREATED_201);
Member omember = resp.getBody().as(Member.class);
System.out.println(omember.toString());
}
postAMember method works fine only with : -
String
POJO Object
Map Object
JsonObject (from GSON library)
But fails with remaining two: -
File - Output is bad request 400
FileInputStream - Output is java.lang.IllegalArgumentException: jdk.internal.ref.PhantomCleanable<?> declares multiple JSON fields named next
And for now I've to make following two more overloaded version of postAMember: -
public Response postAMember(File body) {
return given().spec(this.spec).body(body).when().post(EndPoints.GET_ALL_POST_A_MEMBER).andReturn();
}
public Response postAMember(FileInputStream body) {
return given().spec(this.spec).body(body).when().post(EndPoints.GET_ALL_POST_A_MEMBER).andReturn();
}
Now above two methods generate the response. Any clue what's wrong here? Why the method with generics is not able to take File and FileInputStream?
I've fetched the latest Restassured libraries from maven central repo.
As far as I understand, your generics method will map to body(Object object) of RequestSpecification, then this object will be serialized.
class RequestSpecificationImpl
...
RequestSpecification body(Object object) {
...
this.requestBody = ObjectMapping.serialize(object, requestContentType,
findEncoderCharsetOrReturnDefault(requestContentType), null,
objectMappingConfig(), restAssuredConfig().getEncoderConfig());
...
}
All below kinds of object has no problem with serialization.
String
POJO Object
Map Object
JsonObject (from GSON library)
But
File ---serialize---> full path of FILE
FileInputStream ---serialize---> exception (from Gson/Jackson)
When you add 2 methods, then Rest-Assured correctly map to body(File body) and body(InputStream body) --> no serialization for them --> no issue.

How to Put request with json payload with rest assured

I want to load the JSON file in a body with rest assured but getting error 415.
Can you please help me?
code is:
public class Entitlement_Creation extends BaseClass {
#Test
public void JsonPayload() throws IOException
{
Path json_data = Paths.get("test.json");
byte[] wikiArray = Files.readAllBytes(json_data);
String wikiString = new String(wikiArray);
System.out.println(wikiString);
given()
.contentType(ContentType.JSON)
.accept(ContentType.JSON)
.body(wikiString)
.when()
.put()
.then()
.statusCode(200)
.extract()
.response();
}
from this snippet, I can't see any information about URL of your service.
You should put your URL as an argument to put() method.

how and what to log in MVC application with global action filter

I'm more familiar with the WebApi pipeline, and there is a convenient method whereby I can read and write the the HttpRequest / Response as a string. Easy.
For MVC, it doesn't seem right to log the entire "filter context"... so my question is (1) what do you think are the relevant fields I should log (I'm not that familiar with it at the moment), and (2) how best should I convert these fields to string? xml serialization, json serialization? string builder/stream writer?
Here's my current code that is pretty skinny:
My Web Api:
var requestMessage = await request.Content.ReadAsStringAsync();
_logService.WriteInfo(requestMessage);
var responseMessage = await response.Content.ReadAsStringAsync();
_logService.WriteInfo(responseMessage));
For MVC:
private void LogRequestInfo(ActionExecutingContext filterContext)
{
string controllerName = filterContext.ActionDescriptor.ControllerDescriptor.ControllerName;
string actionName = filterContext.ActionDescriptor.ActionName;
var url = filterContext.HttpContext.Request.Url;
var parameters = filterContext.ActionDescriptor.GetParameters().ToString(); // TODO: need to serialize this
string logTemplate = "Request: controller: {0}, action:{1}, url:{2}, parameters:{3}";
string message = string.Format(logTemplate, controllerName, actionName, url, parameters);
_logService.WriteInfo(message);
}
private void LogResponseInfo(ResultExecutingContext filterContext)
{
string logTemplate = "Response: {0}";
string message = string.Format(logTemplate, filterContext.Result); // TODO: need way to serialize the relevant action result
_logService.WriteInfo(message);
}

XML Schema validation for POST requests with ASP.NET WebAPI

I am trying to find a solution to validate if XML data sent in a POST request are fulfilling a given custom XML schema.
If I use the XmlMediaTypeFormatter delivered with ASP.NET Web API I don't have a schema validation available, as far as I can see. For example: If I have a model type...
public class Order
{
public string Code { get; set; }
public int Quantity { get; set; }
}
...and a POST action in an ApiController...
public HttpResponseMessage Post(Order order)
{
if (ModelState.IsValid)
{
// process order...
// send 200 OK response for example
}
else
// send 400 BadRequest response with ModelState errors in response body
}
...I can post the following "wrong" XML data and will get a 200 OK response nevertheless:
User-Agent: Fiddler
Host: localhost:45678
Content-Type: application/xml; charset=utf-8
<Order> <Code>12345</Nonsense> </Order> // malformed XML
Or:
<Order> <CustomerName>12345</CustomerName> </Order> // invalid property
Or:
<Customer> <Code>12345</Code> </Customer> // invalid root
Or:
"Hello World" // no XML at all
etc., etc.
The only point where I have a validation of the request is model binding: In request example 1, 3 and 4 the order passed into the Post method is null, in example 2 the order.Code property is null which I could invalidate by testing for order == null or by marking the Code property with a [Required] attribute. I could send this validation result back in the response with a 400 "BadRequest" Http status code and validation messages in the response body. But I cannot tell exactly what was wrong and can't distinguish between the wrong XML in example 1, 3 and 4 (no order has been posted, that's the only thing I can see) - for instance.
Requiring that an Order has to be posted with a specific custom XML schema, for example xmlns="http://test.org/OrderSchema.xsd", I would like to validate if the posted XML is valid with respect to this schema and, if not, send schema validation errors back in the response. To achieve this I have started with a custom MediaTypeFormatter:
public class MyXmlMediaTypeFormatter : MediaTypeFormatter
{
// constructor, CanReadType, CanWriteType, ...
public override Task<object> ReadFromStreamAsync(Type type, Stream stream,
HttpContentHeaders contentHeaders, IFormatterLogger formatterLogger)
{
var task = Task.Factory.StartNew(() =>
{
using (var streamReader = new StreamReader(stream))
{
XDocument document = XDocument.Load(streamReader);
// TODO: exceptions must the catched here,
// for example due to malformed XML
XmlSchemaSet schemaSet = new XmlSchemaSet();
schemaSet.Add(null, "OrderSchema.xsd");
var msgs = new List<string>();
document.Validate(schemaSet, (s, e) => msgs.Add(e.Message));
// msgs contains now the list of XML schema validation errors
// I want to send back in the response
if (msgs.Count == 0)
{
var order = ... // deserialize XML to order
return (object)order;
}
else
// WHAT NOW ?
}
});
return task;
}
}
This works so far as long as everything is correct.
But I don't know what to do if msgs.Count > 0. How can I "transfer" this validation result list to the Post action or how can I create a Http response that contains those XML schema validation messages?
Also I am unsure if a custom MediaTypeFormatter is the best extensibility point for such a XML schema validation and if my approach isn't the wrong way. Would possibly a custom HttpMessageHandler/DelegatingHandler be a better place for this? Or is there perhaps something much simpler out of the box?
If I were doing this I wouldn't use the Formatter. The primary goal of a formatter is to convert a wire representation to a CLR type. Here you have an XML document that you want to validate against a schema which is a different task altogether.
I would suggest creating a new MessageHandler to do the validation. Derive from DelegatingHandler and if the content type is application/xml load the content into XDocument and validate. If it fails, then throw a HttpResponseException.
Just add your MessageHandler to the Configuration.MessageHandlers collection and you are set.
The problem with using a derived XmlMediaTypeFormatter is that you are now executing at some point embedded inside the ObjectContent code and it is likely to be tricky to cleanly exit out. Also, making XmlMediaTypeFormatter any more complex is probably not a great idea.
I had a stab at creating the MessageHandler. I did not actually try running this code, so buyer beware. Also, the task stuff gets pretty hairy if you avoid blocking the caller. Maybe someone will clean that code up for me, anyway here it is.
public class SchemaValidationMessageHandler : DelegatingHandler {
private XmlSchemaSet _schemaSet;
public SchemaValidationMessageHandler() {
_schemaSet = new XmlSchemaSet();
_schemaSet.Add(null, "OrderSchema.xsd");
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) {
if (request.Content != null && request.Content.Headers.ContentType.MediaType == "application/xml")
{
var tcs = new TaskCompletionSource<HttpResponseMessage>();
var task = request.Content.LoadIntoBufferAsync() // I think this is needed so XmlMediaTypeFormatter will still have access to the content
.ContinueWith(t => {
request.Content.ReadAsStreamAsync()
.ContinueWith(t2 => {
var doc = XDocument.Load(t2.Result);
var msgs = new List<string>();
doc.Validate(_schemaSet, (s, e) => msgs.Add(e.Message));
if (msgs.Count > 0) {
var responseContent = new StringContent(String.Join(Environment.NewLine, msgs.ToArray()));
tcs.TrySetException(new HttpResponseException(
new HttpResponseMessage(HttpStatusCode.BadRequest) {
Content = responseContent
}));
} else {
tcs.TrySetResult(base.SendAsync(request, cancellationToken).Result);
}
});
});
return tcs.Task;
} else {
return base.SendAsync(request, cancellationToken);
}
}
By trial and error I found a solution (for the WHAT NOW ? placeholder in the question's code):
//...
else
{
PostOrderErrors errors = new PostOrderErrors
{
XmlValidationErrors = msgs
};
HttpResponseMessage response = new HttpResponseMessage(
HttpStatusCode.BadRequest);
response.Content = new ObjectContent(typeof(PostOrderErrors), errors,
GlobalConfiguration.Configuration.Formatters.XmlFormatter);
throw new HttpResponseException(response);
}
...with the response class like this:
public class PostOrderErrors
{
public List<string> XmlValidationErrors { get; set; }
//...
}
That seems to work and the response looks like this then:
HTTP/1.1 400 Bad Request
Content-Type: application/xml; charset=utf-8
<PostOrderErrors xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<XmlValidationErrors>
<string>Some error text...</string>
<string>Another error text...</string>
</XmlValidationErrors>
</PostOrderErrors>

Stream file using ASP.NET MVC FileContentResult in a browser with a name?

Is there a way to stream a file using ASP.NET MVC FileContentResult within the browser with a specific name?
I have noticed that you can either have a FileDialog (Open/Save) or you can stream the file in a browser window, but then it will use the ActionName when you try to save the file.
I have the following scenario:
byte[] contents = DocumentServiceInstance.CreateDocument(orderId, EPrintTypes.Quote);
result = File(contents, "application/pdf", String.Format("Quote{0}.pdf", orderId));
When I use this, I can stream the bytes, but a OPEN/SAVE file dialog is given to the user. I would like to actually stream this file in a browser window.
If I just use the FilePathResult, it shows the file in a browser window, but then when I click on "Save" button to save the file in PDF, it shows me the Action Name as the name of the file.
Has anyone encountered this?
public ActionResult Index()
{
byte[] contents = FetchPdfBytes();
return File(contents, "application/pdf", "test.pdf");
}
and for opening the PDF inside the browser you will need to set the Content-Disposition header:
public ActionResult Index()
{
byte[] contents = FetchPdfBytes();
Response.AddHeader("Content-Disposition", "inline; filename=test.pdf");
return File(contents, "application/pdf");
}
Actually, the absolutely easiest way is to do the following...
byte[] content = your_byte[];
FileContentResult result = new FileContentResult(content, "application/octet-stream")
{
FileDownloadName = "your_file_name"
};
return result;
This might be helpful for whoever else faces this problem. I finally figured out a solution. Turns out, even if we use the inline for "content-disposition" and specify a file name, the browsers still do not use the file name. Instead browsers try and interpret the file name based on the Path/URL.
You can read further on this URL:
Securly download file inside browser with correct filename
This gave me an idea, I just created my URL route that would convert the URL and end it with the name of the file I wanted to give the file. So for e.g. my original controller call just consisted of passing the Order Id of the Order being printed. I was expecting the file name to be of the format Order{0}.pdf where {0} is the Order Id. Similarly for quotes, I wanted Quote{0}.pdf.
In my controller, I just went ahead and added an additional parameter to accept the file name. I passed the filename as a parameter in the URL.Action method.
I then created a new route that would map that URL to the format:
http://localhost/ShoppingCart/PrintQuote/1054/Quote1054.pdf
routes.MapRoute("", "{controller}/{action}/{orderId}/{fileName}",
new { controller = "ShoppingCart", action = "PrintQuote" }
, new string[] { "x.x.x.Controllers" }
);
This pretty much solved my issue.
Previous answers are correct: adding the line...
Response.AddHeader("Content-Disposition", "inline; filename=[filename]");
...will causing multiple Content-Disposition headers to be sent down to the browser. This happens b/c FileContentResult internally applies the header if you supply it with a file name. An alternative, and pretty simple, solution is to simply create a subclass of FileContentResult and override its ExecuteResult() method. Here's an example that instantiates an instance of the System.Net.Mime.ContentDisposition class (the same object used in the internal FileContentResult implementation) and passes it into the new class:
public class FileContentResultWithContentDisposition : FileContentResult
{
private const string ContentDispositionHeaderName = "Content-Disposition";
public FileContentResultWithContentDisposition(byte[] fileContents, string contentType, ContentDisposition contentDisposition)
: base(fileContents, contentType)
{
// check for null or invalid ctor arguments
ContentDisposition = contentDisposition;
}
public ContentDisposition ContentDisposition { get; private set; }
public override void ExecuteResult(ControllerContext context)
{
// check for null or invalid method argument
ContentDisposition.FileName = ContentDisposition.FileName ?? FileDownloadName;
var response = context.HttpContext.Response;
response.ContentType = ContentType;
response.AddHeader(ContentDispositionHeaderName, ContentDisposition.ToString());
WriteFile(response);
}
}
In your Controller, or in a base Controller, you can write a simple helper to instantiate a FileContentResultWithContentDisposition and then call it from your action method, like so:
protected virtual FileContentResult File(byte[] fileContents, string contentType, ContentDisposition contentDisposition)
{
var result = new FileContentResultWithContentDisposition(fileContents, contentType, contentDisposition);
return result;
}
public ActionResult Report()
{
// get a reference to your document or file
// in this example the report exposes properties for
// the byte[] data and content-type of the document
var report = ...
return File(report.Data, report.ContentType, new ContentDisposition {
Inline = true,
FileName = report.FileName
});
}
Now the file will be sent to the browser with the file name you choose and with a content-disposition header of "inline; filename=[filename]".
I hope that helps!
The absolute easiest way to stream a file into browser using ASP.NET MVC is this:
public ActionResult DownloadFile() {
return File(#"c:\path\to\somefile.pdf", "application/pdf", "Your Filename.pdf");
}
This is easier than the method suggested by #azarc3 since you don't even need to read the bytes.
Credit goes to: http://prideparrot.com/blog/archive/2012/8/uploading_and_returning_files#how_to_return_a_file_as_response
** Edit **
Apparently my 'answer' is the same as the OP's question. But I am not facing the problem he is having. Probably this was an issue with older version of ASP.NET MVC?
I adapted it in ASP.NET Core with REST API.
public class FileContentWithFileNameResult : FileContentResult
{
public FileContentWithFileNameResult(byte[] fileContents, string contentType, string fileName)
: base(fileContents, contentType)
{
FileName = fileName;
}
public string FileName { get; private set; }
public override Task ExecuteResultAsync(ActionContext context)
{
var response = context.HttpContext.Response;
response.Headers.Append("Content-Disposition", $"inline; filename={FileName}");
response.Headers.Append("Access-Control-Expose-Headers", "Content-Disposition");
response.Headers.Append("X-Content-Type-Options", "nosniff");
return base.ExecuteResultAsync(context);
}
}
public FileContentResult GetImage(int productId) {
Product prod = repository.Products.FirstOrDefault(p => p.ProductID == productId);
if (prod != null) {
return File(prod.ImageData, prod.ImageMimeType);
} else {
return null;
}
}

Resources