Posted on Leave a comment

How To Return File Download In JAX-RS Application

When developing JAX-RS applications, most of the time, you return JSON to the client. However, there are times you need to return file download. For example, your client may require that when an user access a certain URL, the application must prompt a save file dialog.

If you have trouble with this requirement, look no further. I’m going to show you exactly how to return file download in JAX-RS application.

How to return file download

Let’s consider the following rest enpoint:

    @GET
    @Path("file")
    @Produces(MediaType.TEXT_PLAIN)
    public String download() {
        return "directly to browser";
    }

If you access the URL from the browser, I’m sure you can guess the result. It’s the text “directly to browser” just like this:

Text printed directly to the browser

Now, what if you want instead of print the content to the browser, when an user visit the URL, a file is downloaded instead?

Consider the following code:

    @GET
    @Path("file")
    @Produces(MediaType.TEXT_PLAIN)
    public Response download() {
        return Response.ok("directly to browser")
                .header(HttpHeaders.CONTENT_DISPOSITION, "attachment;")
                .build();
    }

If you visit the browser right now following the same URL as above, a file is downloaded instead.

You may wonder why the name of the file is file.txt? The reason is quite simple: Since I didn’t specify the file name (I’ll show you how later) and from the annotation, I set @Produce to TEXT_PLAIN, so JAX-RS was smart enough to get the path name and the content type to create the file name.

Let’s change the path to something else and change @Produce to XML.

    @GET
    @Path("file-other")
    @Produces(MediaType.APPLICATION_XML)
    public Response download() {
        return Response.ok("directly to browser")
                .header(HttpHeaders.CONTENT_DISPOSITION, "attachment;")
                .build();
    }

Sure enough, the file name is now:

Change file name for the file downloaded

Let’s say now your client is very happy with the progress so far. However, she wants the name of the file to be something else and she doesn’t want to change the URL. What can you do?

Specify the download file’s name

If you need to set name for the downloaded file, simply set it in the header like so:

    @GET
    @Path("file")
    @Produces(MediaType.APPLICATION_XML)
    public Response download() {
        return Response.ok("directly to browser")
                .header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=great_file.xml")
                .build();
    }

Now, if I visit the URL, I’ll get a file named great_file.xml

Specify file name for download file in JAX-RS

Conclusion

Returning file download is a common requirement in web projects. As you can see, by adding Content-Disposition header and set it to attachment, you can make a request return a file download instead of printing out text to the browser. You can also set name for the download file, if you like by specifying filename field.

Posted on Leave a comment

Create Quarkus Project Under 5 Minutes With Live Reloading

Creating Quarkus project is quite simple. You can get started in just a few minutes. In this post, I’m going to show you how you can do that.

My assumption is you have maven installed. If you don’t, please follow the guide to install it here.

After you have maven installed, it very easy to create quarkus project.

Step by step to create Quarkus project

mvn io.quarkus:quarkus-maven-plugin:1.13.2.Final:create \
    -DprojectGroupId=quarkus-project-groupId \
    -DprojectArtifactId=myquarkus-project-artifactId \
    -DprojectVersion=1.0.0-SNAPSHOT \
    -DclassName="com.datmt.QuarkusStart" \
    -Dpath="/get-started"

As you can see, in the create command, you can specify group ID, artifact ID, project version, a starter class name and a resource path.

Remember that if you define a resource path, you need to specify a class name too. In my case, the class name is com.datmt.QuarkusStart.

After you enter the command above, mvn will take a few minutes to scaffold the project for you.

Maven start to create quarkus project
Maven start to create quarkus project

If everything is OK, you should see a message says the project has successfully built:

create quarkus starter project successfully.

Here is the project’s structure. As I use IntelliJ, there is a .idea folder. That’s not part of the project.

Quarkus project structure

There are a few interesting files you can notice. You have Docker files to build the images. You also have tests and most interesting of all is QuarkusStart.java. This is the file we declared when creating the project.

This is the file content:

package com.datmt;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

@Path("/C:/Program Files/Git/get-started")
public class QuarkusStart {

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String hello() {
        return "Hello RESTEasy";
    }
}

As you can see, the path seems wrong. I’ll need to modify it as follow:

package com.datmt;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

@Path("/get-started")
public class QuarkusStart {

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String hello() {
        return "Hey quarkus starter";
    }
}

Running Quarkus application

Now, let’s run the app and check if we can get the the path /get-started up and running. In the root folder of the project, run

./mvnw compile quarkus:dev

If you are on Windows, you may need to remove the ./ part.

If everything is OK, you should see a message says that quarkus is listening on port 8080:

Now, let’s navigate to http://localhost:8080/get-started, we get what is expected:

As mentioned at the title of the post, by running with …quarkus:dev, you will have live reload (or hot reload) enabled. That means as you make changes in code, you can see the changes in the services without stop and run the project again.

Posted on 1 Comment

How To Fix CORS AJAX In Chrome Extension

Recently I return to work on my extension that allow people to save pages to a remote site and also highlight and comment on the page they save (sounds cool?).

When saving a particular page to my server (using fetch/jQuery post), I got this error message:

The error reads like this:

Access to fetch at 'https://openexl.com/api/fr/save-page' from origin 'https://stackoverflow.com' has been blocked by CORS policy: Request header field authorization is not allowed by Access-Control-Allow-Headers in preflight response.

What the extension tried to do was to save a stackoverflow.com page to my server at openexl.com.

We go a CORS error.

Understanding the error

At first, I didn’t read the error message but tried to search for every possible solution online to inject into my Nginx server. After hours of searching, no solution works.

Then I came across this comment on stackoverflow:

Things start to become clear to me. As I looked closers to the preflight request initiated by Chrome, it requested two headers: authorization and content-type. However, in the response headers, the field access-control-allow-headers doesn’t contain such those two.

How to fix CORS error on Nginx

With a clear understanding of the error, the fix came pretty simple. In the server block of my website. I set this:

    location / {
        try_files $uri $uri/ /index.php?$args;
        add_header 'Access-Control-Allow-Origin' '*';
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, HEAD, PUT, PATCH';

        add_header 'Access-Control-Allow-Headers' 'x-requested-with';

        if ($request_method = 'OPTIONS') {


            #...
            add_header 'Access-Control-Allow-Headers' '*';
        }
    }

Now, when Chrome send a preflight request, it knows that all request headers are allowed.

Sure enough, on the next request, I got no CORS error.

Conclusion

Obviously, you can limit the allowed headers to what you need (not setting a wildcard like I did). CORS errors don’t limit just to these headers. You may got different errors but the tip is look exact at the error message an accommodate to the browser’s requests.