Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

imports and typos for examples #21

Open
wants to merge 4 commits into
base: series/0.3
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/

Expand Down
142 changes: 142 additions & 0 deletions doc/custom_codec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
# 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 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]

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 snippet:

```scala

import scodec.Codec
import scodec.codecs._

import spinoco.protocol.http.codec.helper._
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`

val codec: Codec[MyAuthorizationTokenHeader] =
(asciiConstant("Token") ~> (whitespace() ~> utf8String)).xmap(
{ token => TokenAuthorization(token) }, _.token
)

//codec that first tries `Token sometoken` and then falls back to the known alternatives
val customAuthorizationHeader: Codec[HttpHeader] = choice(
codec.upcast[HttpHeader], Authorization.codec.headerCodec
)
}

```

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 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

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 **/
()
}

http.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.