BinaryContent Stream wrapper in Grails - grails

I am trying to write a simple wrapper in grails that will read a binary file from a non-public back-end web-server and makes it available as a stream to a public server.
The first question is what is the best method to handle such a solution considering that the Grails will attempt to render views by default.
I am attempting a solution which leads to an "Attempted read from closed stream." which apparently looks to avoid the view rendering by invoking response but I am not sure if this is a good solution anyway.
I am reading the back-end file through the following code:
def http = new HTTPBuilder(baseUrl)
http.request(method, ContentType.BINARY) {
uri.path = path
uri.query = query
response.success = { resp, inputstream ->
log.info "response status: ${resp.statusLine}"
resp.headers.each { h -> log.info " ${h.name} : ${h.value}" }
return inputstream
}
and later attempt to push it to the client through the following method in a controller:
def binary(){
response.outputStream << getBinaryContent("http://aaa" );
response.flush()
}
Error: "Attempted read from closed stream."

If I'm not mistaken << will close the response so response.flush() is invalid.
Here's the snippet that I use for CSV, just replace with your content type:
byte[] bytes = fileGenerated.getBytes() //fileGenerated is a File
response.setContentType("text/csv")
response.setContentLength(bytes.length)
response.setHeader("Content-Disposition", "attachment; filename=" + fileGenerated.getName())
response.outputStream << bytes

Related

Ruby on rails: error in sending xml message

I try to send a xml file to a web service, but it's not working. The web service supplier has no idea.
If you read the error message, it looks like there is a wrong soap version used, but the supplier tried it with the same xml file and he had no problems with this file. I have no idea what's wrong.
The code:
#Declaration
host = "bar.foo.nl";
path = "/services/setu.asmx";
port = 443;
username = "xxx"
password = "yyy";
ssl = true;
#Create connection
req = Net::HTTP::Post.new(path)
req.basic_auth(username, password)
http = Net::HTTP.new(host, port)
http.use_ssl = ssl
#send file
res = http.request(req, 'D:/test.xml')
#show result
puts res.code
puts res.body
EDIT: The xml file: XML FILE
The error (500 code):
Possible SOAP version mismatch: Envelope namespace
http://ns.hr-xml.org/2007-04-15 was unexpected. Expecting
http://schemas.xmlsoap.org/soap/envelope/.
I don't see any soap declarations in the file you've uploaded, and it looks like the service you're contacting requires it.
Start with http://www.w3schools.com/xml/xml_soap.asp and rework your content to be wrapped in a soap envelope.

Grails Base64 to PDF - render a PDF inline

I have a Grails controller action that calls a service that will return XML that contains a base64 representation of a PDF. Here is an (abbreviated) example sample.
<response status="Success"><messages/><values><pages>8</pages></values><xml/><files><pdf name="FitnessApplication" content-type="application/pdf"><![CDATA[JVBERi0xLjQKJeL ... </files></response>
After we parse that out I want to display that PDF in the browser. So far I am able to decode the string and render it using the file attribute of the render method. This serves up the PDF correctly as a download but I want it to display in the browser (inline) and NOT as a file download.
Map responseMap = new XmlParserHelper().parse( formsResponse.payload )
byte[] decoded = responseMap.files.pdf.decodeBase64()
render( file: decoded, fileName: "${document}.pdf", contentType: "application/pdf", )
I tried setting the content disposition as both an option to render and in the header map but neither seem to do the trick. Does anyone know how I can serve this PDF to the user in the browser?
Just send it in the response. But you need to add the headers on your own. E.g. something like:
response.contentType = "application/pdf"
response.contentLength = FileUtils.copyFile(pdfFile, response.outputStream)
To build on what cfrick said here is the final solution I went with.
// response data
byte[] pdfData = responseMap.files.pdf.decodeBase64()
response.contentType = "application/pdf"
response.setHeader("Content-disposition", "inline; filename='dan.pdf'")
// write to output
OutputStream output = response.outputStream
output.write(pdfData);
output.close();

How can I have Grape return error messages in CSV format?

I have a Rails app and I have implemented api using Grape gem. Now, I created a custom error formatter (CSVFormatter) to return error response in CSV format.
And, also I have this in my application's v2.rb file:
error_formatter :csv, Api::Base::Errors::CSVFormatter
When I hit a url like this:
http://example.com/api/v2/datasets/CODE/data.csv?&trim_start=06/01/99&trim_end=2014-05/28&sort_order=desc
It shows the error in the console like this which is good and means that my custom error formatter is working properly:
Error
trim_start is invalid
trim_end is invalid
But, I just need to download this error message in a csv file. After looking at Grape's documentation, I found a way of setting Content-type and I tried this:
rack = Rack::Response.new(as_csv , 422, { "Content-type" => "text/csv" }).finish
rack[2].body[0]
But, this is not working as I expected.
EDIT:
Looks like there is no clean way of doing it using grape without forcefully overriding the status code according to the answer of Simon. But, one may not wish to do that as it may result other issues in the application like if some other program tries to read the data from the api and gets the incorrect response or so even without knowing why.
You're looking for the Content-Disposition header. Include it in your response like this:
Content-Disposition: attachment; filename=error.csv
And the Web browser will treat the response body as a file to be downloaded (to "error.csv", in this example).
However, modifying your code to do this is complicated by two things:
From the Grape source code it's apparent there's no way to set response headers from within an error formatter, so you'll need to add a custom exception handler that formats the response body and sets the response headers appropriately for each output format you plan to support.
According to my experimentation, browsers will ignore the Content-Disposition header if the HTTP status code indicates an error (e.g. anything in the 400 or 500 range), so the status code will also need to be overridden when the user requests a CSV file.
Try adding this to your API class:
# Handle all exceptions with an error response appropriate to the requested
# output format
rescue_from :all do |e|
# Edit this hash to override the HTTP response status for specific output
# formats
FORMAT_SPECIFIC_STATUS = {
:csv => 200
}
# Edit this hash to add custom headers specific to each output format
FORMAT_SPECIFIC_HEADERS = {
:csv => {
'Content-Disposition' => 'attachment; filename=error.csv'
}
}
# Get the output format requested by the user
format = env['api.format']
# Set the HTTP status appropriately for the requested output format and
# the error type
status = FORMAT_SPECIFIC_STATUS[format] ||
(e.respond_to? :status) && e.status ||
500
# Set the HTTP headers appropriately for the requested format
headers = {
'Content-Type' => options[:content_types][format] || 'text/plain'
}.merge(FORMAT_SPECIFIC_HEADERS[format] || { })
# Format the message body using the appropriate error formatter
error_formatter =
options[:error_formatters][format] || options[:default_error_formatter]
body = error_formatter.call(e.message, nil, options, env)
# Return the error response to the client in the correct format
# with the correct HTTP headers for that format
Rack::Response.new(body, status, headers).finish
end
Now if you configure your API class to handle two different formats (I've picked CSV and plain-text here for simplicity), like this:
module Errors
module CSVErrorFormatter
def self.call(message, backtrace, options, env)
as_csv = "CSV formatter:" + "\n"
message.split(",").each do |msg|
as_csv += msg + "\n"
end
# Note this method simply returns the response body
as_csv
end
end
module TextErrorFormatter
def self.call(message, backtrace, options, env)
as_txt = "Text formatter:" + "\n"
message.split(",").each do |msg|
as_txt += msg + "\n"
end
as_txt
end
end
end
content_type :csv, 'text/csv'
content_type :txt, 'text/plain'
error_formatter :csv, Api::Base::Errors::CSVErrorFormatter
error_formatter :txt, Api::Base::Errors::TextErrorFormatter
You should find your API always returns an error response suitable for the requested format, and triggers the browser to download the response only when CSV format is requested. Naturally this can be extended to support as many formats as you like, by explicitly declaring content types and error formatters.
Note there's one case in which this code doesn't automatically do the right thing, and that's when an error response is invoked directly using error!. In that case you'll have to supply the correct body and headers as part of the call itself. I'll leave extracting the relevant parts of the above code into reusable methods as an exercise for the reader.

Sendgrid adding email to list, getting bad request in grails

I am trying to use following code to send request to sendgrid for adding emails to list, but i always keep getting 404 : Bad request.
def chttps = new HTTPBuilder('https://api.sendgrid.com/api/newsletter/lists/email/add.json?&api_user=myUser&api_key=myKey')
chttps.request( Method.POST, ContentType.JSON ) { req ->
headers.'Content-Type' = 'application/json'
body = [
list : 'testlist',
data : [email : '123Ex#exm.pl', name : '123Ex' ]
]
response.success = { resp, json ->
// response handling here
}
// handler for any failure status code:
}
Following is the error i get:
Bad Request. Stacktrace follows:
groovyx.net.http.HttpResponseException: Bad Request
at groovyx.net.http.HTTPBuilder.defaultFailureHandler(HTTPBuilder.java:609)
at groovyx.net.http.HTTPBuilder.doRequest(HTTPBuilder.java:475)
at groovyx.net.http.HTTPBuilder.doRequest(HTTPBuilder.java:417)
at groovyx.net.http.HTTPBuilder.request(HTTPBuilder.java:366)
at com.farmfresh.brandywine.erp.SendGridService$$EOc3vKl8.addEmailsToRecipientList(SendGridService.groovy:16)
at com.farmfresh.brandywine.erp.CustomerController$_closure5$$EOc3pEvI.doCall(CustomerController.groovy:106)
at org.zkoss.zk.grails.web.ZKGrailsPageFilter.obtainContent(ZKGrailsPageFilter.java:238)
at org.zkoss.zk.grails.web.ZKGrailsPageFilter.doFilter(ZKGrailsPageFilter.java:189)
at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:895)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:918)
at java.lang.Thread.run(Thread.java:662)
can't find what's wrong. Please help.
EDIT1:
So i tried using following code, and it works fine, but still cant specify list of emails in data section:
def chttps = new HTTPBuilder('https://api.sendgrid.com/api/newsletter/lists/email/add.json?&api_user=myUser&api_key=myKey')
def dataa = "list=testlist&data=%7B%22email%22%3A%22examuttample%40gmail.com%22%2C%22name%22%3A%22uttam%22%7D"
chttps.post( body: dataa
) { resp ->
println "${resp}"
resp.headers.each {
println "${it.name} : ${it.value}"
}
println "${resp.data}"
println "http POST Success: ${resp.statusLine}"
}
For adding multiple emails in single request i tried putting following format:
data=%5B%7B%22email%22%3A+%22example1112%40gmail.com%22%2C%22name%22%3A+%22112example%22%7D%2C%7B%22email%22%3A+%22example2222%40gmail.com%22%2C%22name%22%3A+%22222example%22%7D%5D
//encode for :: [{"email": "example1112#gmail.com","name": "112example"},{"email": "example2222#gmail.com","name": "222example"}]
but i keep getting following exception, is there a way around this to add multiple emails.
groovyx.net.http.HttpResponseException: Internal Server Error
at groovyx.net.http.HTTPBuilder.defaultFailureHandler(HTTPBuilder.java:642)
at groovyx.net.http.HTTPBuilder$1.handleResponse(HTTPBuilder.java:494)
at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:1070)
at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:1044)
at groovyx.net.http.HTTPBuilder.doRequest(HTTPBuilder.java:506)
at groovyx.net.http.HTTPBuilder.post(HTTPBuilder.java:343)
at com.farmfresh.brandywine.erp.SendGridService$$EOc9RNxT.addEmailsToRecipientList(SendGridService.groovy:40)
at com.farmfresh.brandywine.erp.CustomerController$_closure5$$EOc9QqKy.doCall(CustomerController.groovy:109)
at org.zkoss.zk.grails.web.ZKGrailsPageFilter.obtainContent(ZKGrailsPageFilter.java:238)
at org.zkoss.zk.grails.web.ZKGrailsPageFilter.doFilter(ZKGrailsPageFilter.java:189)
at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:895)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:918)
at java.lang.Thread.run(Thread.java:662)
It looks like you are trying to post a JSON payload. The SendGrid API currently doesn't support JSON payloads, so you need to send your payload as POST data.
Solved it, turns out json arrays are not acceptable by server instead it accepts multiple "data[]" elements in query string, so all you need to do is call this:
sendgrid.com/api/newsletter/lists/email/add.json?list=testlist&data[]={"email"+%3A+"123Ex1%40exm.pl"%2C+"name"+%3A+"123Ex1"}&data[]={"email"+%3A+"123Ex2%40exm.pl"%2C+"name"+%3A+"123Ex2"}&api_user=myUser&api_key=myKey
Hope this helps someone else.

Sending data in HTTP request's body fails

I am using the HttpConnection class of J2ME in my BlackBerry app to send data to a web server. I need to send the contents of an image in the body of the HTTP request.
This is what I do
Get the bytes of the file in an array
Open HTTP connection
Set content type header as image/jpeg
Get output stream of the connection
Write the bytes to the output stream
Close the output stream and connection
But the image is not uploaded to the server. What could be the problem?
Thanks.
EDIT - Adding code
HttpConnection conn = null;
OutputStream out = null;
try{
conn = new HttpConnection(Connector.open(myURL));
conn.setRequestProperty("Content-Type", "image/jpeg");
conn.setRequestMethod(HttpConnection.POST);
conn.setRequestProperty("Content-Disposition", "form-data");
conn.setRequestProperty("Connection", "Keep-Alive");
out = conn.openOutputStream;
out.write(buffer, 0, buffer.length);
conn.setRequestProperty("Content-Length", buffer.length);
out.flush();
}
catch(Exception e){
e.printStackTrace();
}
finally{
if(out != null)
out.close();
if(conn != null){
System.out.println("" + conn.getResponseCode());
conn.close();
}
}
EDIT
The same code, when I try it with a string, works fine and sends the string to the server. But it is still a problem with the image bytes.
A few things that may be missing from your list:
use HttpConnection.setRequestMethod(HttpConnection.POST) between 2 and 3.
set content length with HttpConnection.setRequestProperty("Content-Length",...) between 5 and 6.
knowing the HTTP request response code can help debug your issues: call HttpConnection.getResponseCode() after you've closed the OutputStream but before you close the HttpConnection.
conn = new HttpConnection(Connector.open(myURL));
This line is wrong. Connection is a factory class that creates new Connection objects by looking it's appropriate protocol implementation.
HttpConnection conn = (HttpConnection) Connector.open(myURL);
The rest of the code seems ok. When you're POSTing, at minimun you need to define the content-type and content-length.
You most definitely need to set all headers before sending the POST data, including the Content-Length header.
Also, make sure you are sending headers valid for requests, and not response-only headers.
You'll need to encode the bytes (preferably Base-64) and send that string. The raw bytes aren't likely to be http safe.
Then on the server, you'll need to decode them back into a byte array and do whatever you were going to do with it (save as file, stuff into database, etc.)

Resources