From b5d47c2cc58cd9dbf6bde0c7aa954c913fdb3bfa Mon Sep 17 00:00:00 2001 From: Pavel Chlupacek Date: Wed, 16 Aug 2017 12:36:04 +0200 Subject: [PATCH 1/4] Added custom codec documentation --- doc/custom_codec.md | 128 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 doc/custom_codec.md diff --git a/doc/custom_codec.md b/doc/custom_codec.md new file mode 100644 index 0000000..f89b419 --- /dev/null +++ b/doc/custom_codec.md @@ -0,0 +1,128 @@ +# Using custom codec for http headers and requests. + +Ocassionally it is required to extends headers supported by fs2-http by some custom headers of user choice. Behind the scenes fs2-http is using scodec library for encoding and decoding codecs. So generally addin any codec is quite straigthforward. + +## Using custom Generic Header + +If you are ok with receiveving your header as simple String value pair, there is simple technique using the `GenericHeader`. This allows you to encode and decode any Http Header with simple string key and value pair, where key is name of the header and value is anything after : in http header. For example : + +``` +Authorization: Token someAuthorizationToken + +``` +may be decoded as + +```scala + +GenericHeader("Authorization", "Token someAuthorizationToken") + +``` + +However to do so we need to supply this codec to the http client and http server. In both cases this is pretty straightforward to do: + +```scala +import spinoco.protocol.http +import spinoco.protocol.http.codec.HttpHeaderCodec + +val genericHeaderAuthCodec: HttpCodec[HttpHeader] = + utf8.xmap[GenericHeader](s => GenericHeader("Authorization", s), _.value).upcast[HttpHeader] + +val headerCodec: Codec[HttpHeader]= + HttpHeaderCodec.codec(Int.MaxValue, ("Authorization" -> genericHeaderAuthCodec)) + + +http.client( + requestCodec = HttpRequestHeaderCodec.codec(headerCodec) + , responseCodec = HttpResponseHeaderCodec.codec(headerCodec) +) map { client => + /** your code with client **/ +} + +http.server( + bindTo = ??? // your ip where you want bind server to + , requestCodec = HttpRequestHeaderCodec.codec(headerCodec) + , responseCodec = HttpResponseHeaderCodec.codec(headerCodec) +) flatMap { server => + /** your code with server **/ +} + + + +``` + +Note that this technique, effectivelly causes to turn-off any already supported Authorization header codecs, which you man not want to. Well, in next section we describe exactly solution for that. + + +## Using custom header codec + +Custom header codecs allow you to write any header codec available or extends it by your own functionality. So lets say we would like to extend Authorization header with our own version of Authorization header while still keeping the current Authroization header codec in place. + +Let's say we ahve our own Authorization header case class : +```scala + +case class MyAuthorizationTokenHeader(token: String) extends HttpHeader + +``` + +First we need to create codec that will encode authorization of our own, and then, when that won't pass, we will try to decode with default. This is quite simply achievable by following code snipped: { + +```scala +import scodec.codecs._ +import spinoco.protocol.http.codec.helper._ +import spinoco.procotol.http.header.value.Authorization + +object MyAuthorizationTokenHeader { + + // this is simple codec to decode essentially line `Token sometoken` + val codec : Codec[MyAuthorizationTokenHeader) = + (asciiConstant("Token") ~> (whitespace() ~> utf8String)).xmap( + { token => MyAuthorizationTokenHeader(token)} + , _.token + ) + + // this is new codec, that will first try to decode by our codec and then if that fails, will use default authroization codec. + val customAuthorizationHeader: Codec[HttpHeader] = choice( + codec.upcast[HttpHeader] + , Authroization.codec + ) + +} + +``` + +Once we have that custom codec setup, we only need to plug it to client and/or server likewise we did for GenericHeader before. For example: + +```scala + +import spinoco.protocol.http +import spinoco.protocol.http.codec.HttpHeaderCodec + + +val headerCodec: Codec[HttpHeader]= + HttpHeaderCodec.codec(Int.MaxValue, ("Authorization" -> MyAuthorizationTokenHeader.customAuthorizationHeader)) + + +http.client( + requestCodec = HttpRequestHeaderCodec.codec(headerCodec) + , responseCodec = HttpResponseHeaderCodec.codec(headerCodec) +) map { client => + /** your code with client **/ +} + +http.server( + bindTo = ??? // your ip where you want bind server to + , requestCodec = HttpRequestHeaderCodec.codec(headerCodec) + , responseCodec = HttpResponseHeaderCodec.codec(headerCodec) +) flatMap { server => + /** your code with server **/ +} + + +``` + +## Summary + +Both of these techniques have own advantages and drawbacks. It is up to user to decide whichever suits best. However, as you may see with a little effort you may plug very complex encoding and decoding schemes (even including any binary data) that your application may require. + + + From fe7918e7993ba7767ace94b7e7781008d00327cc Mon Sep 17 00:00:00 2001 From: Pavel Chlupacek Date: Wed, 16 Aug 2017 12:38:27 +0200 Subject: [PATCH 2/4] Updated readme by adding link to custom headers --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e03b3fa..6bd066c 100644 --- a/README.md +++ b/README.md @@ -183,7 +183,11 @@ The meaning of the individual routes is as follows: - example3 : will match path "/example3" and will consume body to produce `Foo` class. Map is supplied with Foo :: HttpMethod.Value :: HNil - example4 : will match path "/example4" and will match if header `Content-Type` is present supplying that header to map. - example5 : will match path "/example5?count=1&query=sql_query" supplying 1 :: "sql:query" :: HNil to map -- example6 : will match path "/example6" and then evaluating `someEffect` where the result of someEffect will be passed to map +- example6 : will match path "/example6" and then evaluating `someEffect` where the result of someEffect will be passed to map + +### Other documentation and helpful links + +- [Using custom headers](https://github.com/Spinoco/fs2-http/blob/master/doc/custom_codec.md) ### Comparing to http://http4s.org/ From e61ca242d30c93d26728f10cf1c97b795429e9ee Mon Sep 17 00:00:00 2001 From: Martijn Hoekstra Date: Wed, 16 Aug 2017 14:25:08 +0200 Subject: [PATCH 3/4] include full imports for the first example --- doc/custom_codec.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/doc/custom_codec.md b/doc/custom_codec.md index f89b419..cb6e08a 100644 --- a/doc/custom_codec.md +++ b/doc/custom_codec.md @@ -21,8 +21,15 @@ GenericHeader("Authorization", "Token someAuthorizationToken") However to do so we need to supply this codec to the http client and http server. In both cases this is pretty straightforward to do: ```scala -import spinoco.protocol.http +import scodec.Codec +import scodec.codecs.utf8 + +import spinoco.fs2.http + import spinoco.protocol.http.codec.HttpHeaderCodec +import spinoco.protocol.http.codec.HttpRequestHeaderCodec +import spinoco.protocol.http.header.GenericHeader +import spinoco.protocol.http.header.HttpHeader val genericHeaderAuthCodec: HttpCodec[HttpHeader] = utf8.xmap[GenericHeader](s => GenericHeader("Authorization", s), _.value).upcast[HttpHeader] @@ -45,8 +52,6 @@ http.server( ) flatMap { server => /** your code with server **/ } - - ``` From 83d9d21a88e67290040cbe5846fccbbf40dbd9b6 Mon Sep 17 00:00:00 2001 From: Martijn Hoekstra Date: Wed, 16 Aug 2017 15:45:06 +0200 Subject: [PATCH 4/4] make second example compile imports and typos --- doc/custom_codec.md | 71 +++++++++++++++++++++++++-------------------- 1 file changed, 40 insertions(+), 31 deletions(-) diff --git a/doc/custom_codec.md b/doc/custom_codec.md index cb6e08a..10ffe74 100644 --- a/doc/custom_codec.md +++ b/doc/custom_codec.md @@ -69,28 +69,34 @@ case class MyAuthorizationTokenHeader(token: String) extends HttpHeader ``` -First we need to create codec that will encode authorization of our own, and then, when that won't pass, we will try to decode with default. This is quite simply achievable by following code snipped: { +First we need to create codec that will encode authorization of our own, and then, when that won't pass, we will try to decode with default. This is quite simply achievable by following code snippet: ```scala + +import scodec.Codec import scodec.codecs._ + import spinoco.protocol.http.codec.helper._ -import spinoco.procotol.http.header.value.Authorization +import spinoco.protocol.http.codec.HttpHeaderCodec +import spinoco.protocol.http.header.HttpHeader +import spinoco.protocol.http.header.Authorization + +case class MyAuthorizationTokenHeader(token: String) extends HttpHeader { + val name = "Authorization" +} object MyAuthorizationTokenHeader { + //codec for `Token sometoken` - // this is simple codec to decode essentially line `Token sometoken` - val codec : Codec[MyAuthorizationTokenHeader) = - (asciiConstant("Token") ~> (whitespace() ~> utf8String)).xmap( - { token => MyAuthorizationTokenHeader(token)} - , _.token + val codec: Codec[MyAuthorizationTokenHeader] = + (asciiConstant("Token") ~> (whitespace() ~> utf8String)).xmap( + { token => TokenAuthorization(token) }, _.token ) - - // this is new codec, that will first try to decode by our codec and then if that fails, will use default authroization codec. + + //codec that first tries `Token sometoken` and then falls back to the known alternatives val customAuthorizationHeader: Codec[HttpHeader] = choice( - codec.upcast[HttpHeader] - , Authroization.codec + codec.upcast[HttpHeader], Authorization.codec.headerCodec ) - } ``` @@ -99,35 +105,38 @@ Once we have that custom codec setup, we only need to plug it to client and/or s ```scala -import spinoco.protocol.http + +import scodec.Codec import spinoco.protocol.http.codec.HttpHeaderCodec +import spinoco.protocol.http.codec.HttpResponseHeaderCodec +import spinoco.protocol.http.codec.HttpRequestHeaderCodec +import spinoco.protocol.http.header.HttpHeader - -val headerCodec: Codec[HttpHeader]= - HttpHeaderCodec.codec(Int.MaxValue, ("Authorization" -> MyAuthorizationTokenHeader.customAuthorizationHeader)) - +import spinoco.fs2.http + +//codec for the full header, including header name +val headerCodec: Codec[HttpHeader] = + HttpHeaderCodec.codec(Int.MaxValue, ("Authorization" -> MyAuthorizationTokenHeader.customAuthorizationHeader)) http.client( - requestCodec = HttpRequestHeaderCodec.codec(headerCodec) - , responseCodec = HttpResponseHeaderCodec.codec(headerCodec) -) map { client => - /** your code with client **/ + requestCodec = HttpRequestHeaderCodec.codec(headerCodec), + responseCodec = HttpResponseHeaderCodec.codec(headerCodec) +) map { client => + /** your code with client **/ + () } http.server( - bindTo = ??? // your ip where you want bind server to - , requestCodec = HttpRequestHeaderCodec.codec(headerCodec) - , responseCodec = HttpResponseHeaderCodec.codec(headerCodec) -) flatMap { server => - /** your code with server **/ + bindTo = ???, // your ip where you want bind server to + requestCodec = HttpRequestHeaderCodec.codec(headerCodec), + responseCodec = HttpResponseHeaderCodec.codec(headerCodec) +) map { server => + /** your code with server **/ + () } - ``` ## Summary -Both of these techniques have own advantages and drawbacks. It is up to user to decide whichever suits best. However, as you may see with a little effort you may plug very complex encoding and decoding schemes (even including any binary data) that your application may require. - - - +Both of these techniques have own advantages and drawbacks. It is up to user to decide whichever suits best. However, as you may see with a little effort you may plug very complex encoding and decoding schemes (even including any binary data) that your application may require.