I recently learned an interesting feature in the Goa framework, which we use for some of our services. How can we return a ZIP file which is created by a service function implementation, but also include additional custom headers in the response?
Big thanks to one of the Goa creators/contributors Raphael Simon, who helped me with this issue very quickly in the Goa Slack channel!
Encoding bytes
Goa provides default response encoders for JSON, XML, GOB and plain text/bytes. In my case the plain bytes response was almost enough, but I wanted to have additional response headers returned to the client.
As I wanted to have Content-Type: application/zip
header, I wrote a small encoder
using the default TextEncoder as example and everything worked fine.
What’s the problem
I wanted to have one additional response header specifying the name of the ZIP file
which was dependent on some internal resource characteristics. More specific, I wanted
to include a response header Content-Disposition: attachment; filename="myfile.zip"
where myfile.zip
could be different with every request.
The simple text/bytes encoder cannot do this, because there’s no way I can give it more
specific values from the Service implementation - I can only return []byte
slice as
response.
With Goa, if you need to return more specific headers, you have to define them in a Goa DSL response type.
|
|
Now my small custom bytes encoder receives this object, but the body is wrapped in a generated wrapper
object which I must cast to the specific ExportZipResult
type in order to get the body bytes.
It didn’t seem natural as it means that this bytes encoder will only be usable for that specific
function and won’t be useful for other similar functions. That’s when I decided to write in the Slack
channel and to my surprise the creator of Goa responded within a few hours.
The solution
It turned out that Goa provides a custom DSL function which instructs the code generation process
to skip the creation of any response encoder for that function. Instead, you must implement a function
which returns an object containing all header values which you’d like to return,
plus an io.ReadCloser
for the bytes that you want to return to the client.
|
|
Now there is no need for writing any custom Encoder and the function that implements the creation of the ZIP archive looks like this.
|
|
And it works like charm! :)