Search code examples
apigroovyrx-javaratpack

Post API using Ratpack and Groovy giving 405 Error and RxJava methods not working


I am building an API using Ratpack and Groovy. The POST API is always giving:

405-Method not Found Error

This is a snippet from POST Endpoint Handler. In this code, promiseSingle, then, observe, map, doOnNext, doOnError, etc.

RxJAVA functions are not working. Is there any reason why RxJava methods are not working?

saveJsonAsData(context, id)
            .promiseSingle()
            .then { Data updateddata ->
                context.response.headers
                    .add(HttpHeaderNames.LOCATION, "/api/save/${updateddata.id}/${updateddata.value}")
                context.response.status(HttpResponseStatus.CREATED.code())
                    .send()
            }
    }

    protected Observable<Data> saveJsonAsData(GroovyContext context, String id) {
        context.request.body.observe()
            .map { TypedData typedData -> extractData(context, typedData) }
            .doOnNext { Data data ->
                data.id = id
                validatorWrapper.validate(data)
            }
            .flatMap(data.&save as Func1)
            .doOnError { Throwable throwable -> log.error("Error saving data", throwable) }
    }

Solution

  • The issue is not so much with Rx as it is with the usage of the Context.

    You should try to keep the response handling logic within your Handler, that is don't pass the Context around, rather get the objects you need and pass them to your services.

    As an example

    path('myendpoint') { MyRxService service ->
      byMethod {
        get {
          // do something when request is GET
        }
        post {
          request.body.map { typedData ->
            extractItem(typeData) // extract your item from the request first
          }.flatMap { item ->
              service.saveJsonAsItemLocation(item).promiseSingle() // then once it's extracted pass the item to your "saveJsonAsItemLocation" method
          }.then { ItemLocationStore updatedItem ->
              response.headers.add(HttpHeaderNames.LOCATION, "/itemloc/v1/save/${updatedItem.tcin}/${updatedItem.store}")
              context.response.status(HttpResponseStatus.CREATED.code()).send()
          }
        }
      }
    }
    

    My guess is that you have something like this:

    get {
     // get stuff
    }
    
    post {
      // post stuff
    }
    

    The reason this doesn't work is that Ratpack doesn't use Routing Table for handling incoming requests, instead it uses chain delegation. The get {} binds to root path and GET http method and post {} binds to root path and POST http method. Because get {} matches the path, Ratpack considers the handler matched and since the handler is for GET it considers it a 405.

    There are chain methods available that binds regardless of HTTP Method such as all {} and path {}. Chain#all will handle all paths and methods where as Chain#path(String) matches against specific path.

    Hope this helps.