Skip to content

Commit

Permalink
fix: Various fixes to make http-recorder usable again. (#2983)
Browse files Browse the repository at this point in the history
As it stands, http-recorder only works with GET requests without a body.
This situation is caused by two issues:
- The hash code of the body is not computed correctly. `Arrays.hashcode`
should be used to compute an hash code over the content of the array as
the `hashcode` method on the array itself only relies on the address of
the object.
- `RedirectToRxEndpoint` only exposes a `GET` endpoint. No other verbs
can pass-through. I don't know if there's a better way to fix it, but
the quick fix I'm proposing works.
  • Loading branch information
NicolasRichard authored May 25, 2023
1 parent a7149a3 commit aacb665
Show file tree
Hide file tree
Showing 4 changed files with 52 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
package wvlet.airframe.http
import wvlet.airspec.AirSpec

import java.nio.charset.StandardCharsets

/**
*/
class HttpMessageTest extends AirSpec {
Expand Down Expand Up @@ -160,4 +162,12 @@ class HttpMessageTest extends AirSpec {
r.acceptsMsgPack shouldBe true
r.acceptsJson shouldBe false
}

test("hashcode of the body gets computed correctly") {
val json = "{\"drink\": \"coffee\"}";
val req1 = Http.request("/drinks").withContent(json)
val req2 = Http.request("/drinks").withContent(json.getBytes(StandardCharsets.UTF_8))

req1.message.contentHash shouldBe req2.message.contentHash
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import wvlet.airframe.msgpack.spi.MsgPack

import java.nio.charset.StandardCharsets
import java.time.Instant
import java.util
import scala.language.experimental.macros

trait HttpMessage[Raw] extends HttpMessageBase[Raw] {
Expand Down Expand Up @@ -141,7 +142,7 @@ object HttpMessage {
def nonEmpty: Boolean = !isEmpty
def toContentString: String
def toContentBytes: Array[Byte]
def contentHash: Int = toContentBytes.hashCode()
def contentHash: Int = util.Arrays.hashCode(toContentBytes)
}

object Message {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
*/
package wvlet.airframe.http.router

import wvlet.airframe.http.{Endpoint, HttpMessage, RPCContext, RxHttpEndpoint}
import wvlet.airframe.http.{Endpoint, HttpMessage, HttpMethod, RPCContext, RxHttpEndpoint}
import wvlet.airframe.rx.Rx
import wvlet.log.LogSupport

Expand All @@ -22,8 +22,31 @@ import wvlet.log.LogSupport
* @param endpoint
*/
class RedirectToRxEndpoint(endpoint: RxHttpEndpoint) extends LogSupport {
@Endpoint(path = "/*path")
def process(): Rx[HttpMessage.Response] = {
@Endpoint(path = "/*path", method = HttpMethod.GET)
def get(): Rx[HttpMessage.Response] = process()

@Endpoint(path = "/*path", method = HttpMethod.POST)
def post(): Rx[HttpMessage.Response] = process()

@Endpoint(path = "/*path", method = HttpMethod.PUT)
def put(): Rx[HttpMessage.Response] = process()

@Endpoint(path = "/*path", method = HttpMethod.HEAD)
def head(): Rx[HttpMessage.Response] = process()

@Endpoint(path = "/*path", method = HttpMethod.PATCH)
def patch(): Rx[HttpMessage.Response] = process()

@Endpoint(path = "/*path", method = HttpMethod.DELETE)
def delete(): Rx[HttpMessage.Response] = process()

@Endpoint(path = "/*path", method = HttpMethod.OPTIONS)
def options(): Rx[HttpMessage.Response] = process()

@Endpoint(path = "/*path", method = HttpMethod.TRACE)
def trace(): Rx[HttpMessage.Response] = process()

private def process(): Rx[HttpMessage.Response] = {
val req = RPCContext.current.httpRequest
endpoint.apply(req)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
*/
package wvlet.airframe.http.router

import wvlet.airframe.http.{Http, HttpMessage, RxHttpEndpoint, RxRouter}
import wvlet.airframe.http._
import wvlet.airframe.rx.Rx
import wvlet.airframe.surface.Surface
import wvlet.airspec.AirSpec
Expand All @@ -31,7 +31,19 @@ class CustomEndpointTest extends AirSpec {
router shouldMatch { case RxRouter.EndpointNode(controllerSurface, methodSurfaces, Some(ep)) =>
ep shouldBeTheSameInstanceAs endpoint
controllerSurface shouldBe Surface.of[RedirectToRxEndpoint]
methodSurfaces(0).name shouldBe "process"

val expectedMethods = Set(
HttpMethod.GET.toLowerCase,
HttpMethod.POST.toLowerCase,
HttpMethod.PATCH.toLowerCase,
HttpMethod.DELETE.toLowerCase,
HttpMethod.PUT.toLowerCase,
HttpMethod.OPTIONS.toLowerCase,
HttpMethod.TRACE.toLowerCase,
HttpMethod.HEAD.toLowerCase
)

methodSurfaces.map(_.name).toSet == expectedMethods
}
}
}

0 comments on commit aacb665

Please sign in to comment.