Michael Hoffman
Michael Hoffman

Reputation: 454

Springfox 404 Error When Testing API from Swagger UI

Investigating Springfox and Swagger UI, but I am facing an issue. I am using the Spring Boot REST example project as the foundation for my PoC. I'm running JDK 8 and the project leverages Gradle.

First, here are the file contents for the project:

build.gradle

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:1.2.7.RELEASE")
    }
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'spring-boot'

jar {
    baseName = 'gs-rest-service'
    version =  '0.1.0'
}

repositories {
    mavenCentral()
    jcenter()
}

sourceCompatibility = 1.8
targetCompatibility = 1.8

dependencies {
    compile("org.springframework.boot:spring-boot-starter-web")
    compile("io.springfox:springfox-swagger2:2.2.2")
    compile("io.springfox:springfox-swagger-ui:2.2.2")
    testCompile("junit:junit")
}

task wrapper(type: Wrapper) {
    gradleVersion = '2.3'
}

GreetingController.java

package hello;

import java.util.concurrent.atomic.AtomicLong;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class GreetingController {

    private static final String template = "Hello, %s!";
    private final AtomicLong counter = new AtomicLong();

    @RequestMapping("/greeting")
    public Greeting greeting(@RequestParam(value="name", defaultValue="World") String name) {
        return new Greeting(counter.incrementAndGet(),
                            String.format(template, name));
    }
}

Greeting.java

package hello;

public class Greeting {

    private final long id;
    private final String content;

    public Greeting(long id, String content) {
        this.id = id;
        this.content = content;
    }

    public long getId() {
        return id;
    }

    public String getContent() {
        return content;
    }
}

Application.java

package hello;

import static com.google.common.collect.Lists.newArrayList;
import static springfox.documentation.schema.AlternateTypeRules.newRule;

import java.time.LocalDate;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.context.request.async.DeferredResult;

import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.builders.ResponseMessageBuilder;
import springfox.documentation.schema.ModelRef;
import springfox.documentation.schema.WildcardType;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import com.fasterxml.classmate.TypeResolver;

@SpringBootApplication
@EnableSwagger2
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Autowired
    private TypeResolver typeResolver;

    @Bean
    public Docket greetingApi() {
        return new Docket(DocumentationType.SPRING_WEB)
            .select()
            .apis(RequestHandlerSelectors.any())
            .paths(PathSelectors.any())
            .build()
            .pathMapping("/")
            .directModelSubstitute(LocalDate.class, String.class)
            .genericModelSubstitutes(ResponseEntity.class)
            .alternateTypeRules(newRule(typeResolver.resolve(DeferredResult.class,
                typeResolver.resolve(ResponseEntity.class, WildcardType.class)),
                typeResolver.resolve(WildcardType.class)))
            .useDefaultResponseMessages(false)
            .globalResponseMessage(RequestMethod.GET,
                newArrayList(new ResponseMessageBuilder()
                    .code(500)
                    .message("500 message")
                    .responseModel(new ModelRef("Error"))
                    .build()))
            .enableUrlTemplating(true);
    }

}

Here is the issue I am facing. When I build and run the application, I can successfully navigate to the Swagger UI page (http://localhost:8080/swagger-ui.html). When I expand greeting-controller, I see the different methods and expand "get /greeting{?name}". The Get section has the following content:

Response Class (Status 200)

Model

{
  "content": "string",
  "id": 0
}

Response Content Type: */*

Parameters
parameter = name, value = World, parameter type = query, data type = string

When I click the "Try It Out" button, I see the following:

curl = curl -X GET --header "Accept: */*" "http://localhost:8080/greeting{?name}?name=World"

request url = http://localhost:8080/greeting{?name}?name=World

repsonse body = {
  "timestamp": 1446418006199,
  "status": 404,
  "error": "Not Found",
  "message": "No message available",
  "path": "/greeting%7B"
}

response code = 404

response headers = {
  "server": "Apache-Coyote/1.1",
  "content-type": "application/json;charset=UTF-8",
  "transfer-encoding": "chunked",
  "date": "Sun, 01 Nov 2015 22:46:46 GMT"
}

At first glance, it looks like for some reason that Springfox/Swagger is not correctly replacing the placeholder for {?name}. My question is, how do I configure it to do so, if that is in fact the issue, so that I can successfully test out the service from the Swagger UI page?

Upvotes: 1

Views: 4031

Answers (1)

Dilip Krishnan
Dilip Krishnan

Reputation: 5487

In your Application class changing the enableUrlTemplating to false will fix your problem.

@Bean
public Docket greetingApi() {
    return new Docket(DocumentationType.SPRING_WEB)
        //...
        .enableUrlTemplating(false);
}

Just a little bit of background on that flag. That flag is to support RFC 6570 without which operations that differ only by query string parameters will not show up correctly per spec. In the next iteration of the swagger spec there are plans to address that issue. That is the reason for enableUrlTemplating to be marked as an incubating feature.

Upvotes: 5

Related Questions