Peter Jaloveczki
Peter Jaloveczki

Reputation: 2089

Only @GET requests work for my RESTFUL application

As the title suggests I have a pretty simple issue where I have two endpoints set up on a server and I am able to execute @GET commands against them, but @POST and @DELETE messages regularly return 404 response. I might be missing something obvious here.

Here are my POM dependencies:

    <dependency>
        <groupId>javax.ws.rs</groupId>
        <artifactId>javax.ws.rs-api</artifactId>
        <version>2.0.1</version>
    </dependency>

    <dependency>
        <groupId>org.glassfish.jersey.core</groupId>
        <artifactId>jersey-common</artifactId>
    </dependency>

    <dependency>
        <groupId>org.glassfish.jersey.core</groupId>
        <artifactId>jersey-client</artifactId>
    </dependency>

    <dependency>
        <groupId>org.glassfish.jersey.core</groupId>
        <artifactId>jersey-server</artifactId>
    </dependency>

    <dependency>
        <groupId>org.glassfish.jersey.containers</groupId>
        <artifactId>jersey-container-grizzly2-http</artifactId>
    </dependency>

    <dependency>
        <groupId>org.glassfish.jersey.media</groupId>
        <artifactId>jersey-media-json-jackson</artifactId>
    </dependency>

    <dependency>
        <groupId>org.glassfish.jersey.test-framework</groupId>
        <artifactId>jersey-test-framework-util</artifactId>
    </dependency>

    <dependency>
        <groupId>com.google.code.gson</groupId>
        <artifactId>gson</artifactId>
        <version>2.4</version>
    </dependency>

    <dependency>
        <groupId>org.glassfish.jersey.test-framework.providers</groupId>
        <artifactId>jersey-test-framework-provider-bundle</artifactId>
        <type>pom</type>
    </dependency>

    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-lang3</artifactId>
        <version>3.4</version>
    </dependency>

    <dependency>
        <groupId>commons-io</groupId>
        <artifactId>commons-io</artifactId>
        <version>2.4</version>
    </dependency>

    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>

Here is the sample interface I use:

import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

/**
 * The interface shared to airport weather collection systems.
 *
 * @author code test administartor
 */
public interface TestEndpoint {

    /**
     * This works like a charm
     */
    @GET
    @Path("/ping")
    Response ping();

    /**
     * This does NOT work keeps throwing 404
     */
    @POST
    @Path("/weather/{iata}/{pointType}")
    Response updateWeather(@PathParam("iata") String iataCode,
                           @PathParam("pointType") String pointType,
                           String datapointJson);

    /**
     * This works like a charm
     */
    @GET
    @Path("/airports")
    @Produces(MediaType.APPLICATION_JSON)
    Response getAirports();

   /**
     * This works like a charm
     */
    @GET
    @Path("/airport/{iata}")
    @Produces(MediaType.APPLICATION_JSON)
    Response getAirport(@PathParam("iata") String iata);

    /**
     * This does NOT work keeps throwing 404
     */
    @POST
    @Path("/airport/{iata}/{lat}/{long}")
    Response addAirport(@PathParam("iata") String iata,
                        @PathParam("lat") String latString,
                        @PathParam("long") String longString);

    /**
     * This does NOT work keeps throwing 404
     */
    @DELETE
    @Path("/airport/{iata}")
    Response deleteAirport(@PathParam("iata") String iata);

    /**
     * This works like a charm
     */
    @GET
    @Path("/exit")
    Response exit();
}

The implementation for this interface:

import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;

import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.core.Response;
import java.util.Set;
import java.util.function.Function;
import java.util.logging.Logger;
import java.util.stream.Collectors;


/**
 * A REST implementation of the WeatherCollector API. Accessible only to airport weather collection
 * sites via secure VPN.
 *
 * @author code test administrator
 */

@Path("/collect")
public class TestEndpointImpl implements TestEndpoint {
    public final static Logger LOGGER = Logger.getLogger(TestEndpointImpl .class.getName());

    /**
     * shared gson json to object factory
     */
    public final static Gson gson = new Gson();

    @Override
    public Response ping() {
        return Response.status(Response.Status.OK).entity("ready").build();
    }

    @Override
    public Response updateWeather(@PathParam("iata") String iataCode,
                                  @PathParam("pointType") String pointType,
                                  String datapointJson) {
        try {
            ....
            return Response.status(Response.Status.OK).build();
        } catch (JsonSyntaxException e) {
            LOGGER.severe(String.format("The given json input is not well formatted: %s", datapointJson));
            return Response.status(Response.Status.NOT_ACCEPTABLE).build();
        } catch (IllegalArgumentException e) {
            LOGGER.severe("Invalid arguments.");
            return Response.status(Response.Status.NOT_ACCEPTABLE).build();
        } catch (Exception e) {
            LOGGER.severe("Internal server error");
            return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
        }
    }

    @Override
    public Response getAirports() {
        ....
        return Response.status(Response.Status.OK).entity(iatas).build();
    }

    @Override
    public Response getAirport(@PathParam("iata") String iata) {
        try {
           ....
        } catch (IllegalArgumentException ex) {
            LOGGER.warning("Invalid values submitted.");
            return Response.status(Response.Status.NOT_ACCEPTABLE).build();
        } catch (Exception e) {
            LOGGER.severe("Internal server error");
            return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
        }
    }

    @Override
    public Response addAirport(@PathParam("iata") String iata,
                               @PathParam("lat") String latParam,
                               @PathParam("long") String longParam) {
        try {
           ....
            return Response.status(Response.Status.OK).build();
        } catch (IllegalArgumentException ex) {
            LOGGER.warning("Invalid values submitted.");
            return Response.status(Response.Status.NOT_ACCEPTABLE).build();
        } catch (Exception e) {
            LOGGER.severe("Internal server error");
            return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
        }
    }

    @Override
    public Response deleteAirport(@PathParam("iata") String iata) {
        try {
            ....
            return Response.status(Response.Status.OK).build();
        } catch (IllegalArgumentException ex) {
            LOGGER.warning("Invalid values submitted.");
            return Response.status(Response.Status.NOT_ACCEPTABLE).build();
        } catch (Exception e) {
            LOGGER.severe("Internal server error");
            return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
        }
    }

    @Override
    public Response exit() {
        System.exit(0);
        return Response.noContent().build();
    }

}

The server that runs the server:

public static void main(String[] args) {
        try {
            System.out.println("Starting Weather App local testing server: " + BASE_URL);
            System.out.println("Not for production use");

            final ResourceConfig resourceConfig = new ResourceConfig();
            resourceConfig.register(TestEndpointImpl.class);
            final HttpServer server = GrizzlyHttpServerFactory.createHttpServer(URI.create(BASE_URL), resourceConfig, false);

            Runtime.getRuntime().addShutdownHook(new Thread(() -> {
                server.shutdownNow();
            }));

            HttpServerProbe probe = new HttpServerProbe.Adapter() {
                public void onRequestReceiveEvent(HttpServerFilter filter, Connection connection, Request request) {
                    System.out.println(request.getRequestURI());
                }
            };

            server.getServerConfiguration().getMonitoringConfig().getWebServerConfig().addProbes(probe);
            System.out.println(format("Weather Server started.\n url=%s\n", BASE_URL));
            server.start();

            // blocks until exit
            Thread.currentThread().join();
            server.shutdown();
        } catch (IOException | InterruptedException ex) {
            Logger.getLogger(TestServer.class.getName()).log(Level.SEVERE, null, ex);
        }

    }

My test client

private static final String BASE_URI = "http://localhost:9090";

    /** end point to supply updates */
    private WebTarget collect;

    public TestClient() {
        Client client = ClientBuilder.newClient();
        collect = client.target(BASE_URI + "/collect");
    }

    public void pingCollect() {
        WebTarget path = collect.path("/ping");
        Response response = path.request().get();
        System.out.print("collect.ping: " + response.readEntity(String.class) + "\n");
    }

    public void delete(String iata) {
        WebTarget path = collect.path("/airport/" + iata);
        Response response = path.request().delete();
        System.out.println("Deleting airport result: " + response.getStatus());
    }

    public void populate(String pointType, int first, int last, int mean, int median, int count) {
        WebTarget path = collect.path("/weather/BOS/" + pointType);
        DataPoint dp = new DataPoint.Builder()
                .withFirst(first).withLast(last).withMean(mean).withMedian(median).withCount(count)
                .build();
        Response post = path.request().post(Entity.entity(dp, "application/json"));
    }

    public void exit() {
        try {
            collect.path("/exit").request().get();
        } catch (Throwable t) {
            // swallow
        }
    }

    public static void main(String[] args) {
        TestClientwc = new TestClient();
        wc.pingCollect();
        wc.populate("wind", 0, 10, 6, 4, 20);

        wc.delete("MMU");

        wc.exit();
        System.out.print("complete");
        System.exit(0);
    }
}

I threw out a different client that did querying but that works the same ways. @GET requests no problem @POST / @DELETE requests not working as used above. Anything I'm doing wrong here?

Upvotes: 1

Views: 184

Answers (1)

Matthias Steinbauer
Matthias Steinbauer

Reputation: 1776

I suppose that the annotations must be on the service class and not on the interface. This means the annotations currently configured on your interface need to be moved to the implementing class TestEndpointImpl.

Further I can suggest the following: In your client you specify application/json as the mime-type for content posted to the service. However, on the service side you would require a @Consumes annotation to register the method to be chosen for that particular content type.

As a bonus you could then use automatic JSON to Java object mapping and provide a POJO parameter instead of just String and parsing the JSON manually.

Btw. I usually test my services in Eclipse with JBoss Webservice tester or the RESTClient Firefox https://addons.mozilla.org/en-US/firefox/addon/restclient/

Upvotes: 4

Related Questions