Exclude specific HEADERS from Response - scala

how to remove headers from Response (SimpleResult)
the example of code:
def NoCache[A](action: Action[A]): Action[A] = Action(action.parser) { request =>
action(request) match {
case s: SimpleResult[_] =>
s.withHeaders(PRAGMA -> "no-cache")
// remove all headers with name "ETAG" HERE ??
case result => result
}
}
i did not find this functionality in documentation.
thanks.

Since SimpleResult and ResponseHeader are both case classes, you can copy them to modify the headers:
...
val headers = s.header.headers - ETAG + (PRAGMA -> "no-cache")
s.copy(header = s.header.copy(headers = headers))
...

Related

How to add custom error responses in Http4s?

Whenever I hit unknown route in my http4s application it returns 404 error page with Content-Type: text/plain and body:
Not found
How can I force it to always return body as JSON with Content-Type: application/json?
{"message": "Not found"}
I figured out that when I assembly httpApp I can map over it and "adjust" responses:
val httpApp = Router.publicRoutes[F].orNotFound.map(ErrorTranslator.handle)
where ErrorTranslator just detects responses with status code of client error and Content-Type which is not application/json and then just wraps body into JSON:
object ErrorTranslator {
val ContentType = "Content-Type"
val ApplicationJson = "application/json"
private def translate[F[_]: ConcurrentEffect: Sync](r: Response[F]): Response[F] =
r.headers.get(CaseInsensitiveString(ContentType)).map(_.value) match {
case Some(ApplicationJson) => r
case _ => r.withEntity(r.bodyAsText.map(ErrorView(_))) //wrap reponse body into enity
}
def handle[F[_]: ConcurrentEffect: Sync]: PartialFunction[Response[F], Response[F]] = {
case Status.ClientError(r) => translate(r)
case r => r
}
}
It works, but I wonder if there is maybe some less convoluted solution?
It would be also great if a solution could "translate" other errors, like 400 Bad request into JSON, similarily to presented code.
You can also make it with value and mapF function:
val jsonNotFound: Response[F] =
Response(
Status.NotFound,
body = Stream("""{"error": "Not found"}""").through(utf8Encode),
headers = Headers(`Content-Type`(MediaType.application.json) :: Nil)
)
val routes: HttpRoutes[F] = routes.someRoutes().mapF(_.getOrElse(jsonNotFound))
I suppose you have defined your routes in a similar fashion, then you can add a default case statement
HttpRoutes.of[IO] {
case GET -> Root / "api" =>
Ok()
case _ -> Root =>
// Your default route could be done like this
Ok(io.circe.parser.parse("""{"message": "Not Found"}"""))
}

Appending to given formdata for http post

I am calling the following helper function from an action to make a syncronous HTTP-post with a special key-value pair appended to the URL-parameters in formdata:
def synchronousPost(url: String, formdata: Map[String, Seq[String]], timeout: Duration=Duration.create(30, TimeUnit.SECONDS)): String = {
import ExecutionContext.Implicits.global
val params = formdata.map { case (k, v) => "%s=%s".format(k, URLEncoder.encode(v.head, "UTF-8")) }.mkString("&")
val future: Future[WSResponse] = ws.url(url).
withHttpHeaders(("Content-Type", "application/x-www-form-urlencoded")).
post(params)
try {
Await.result(future, timeout)
future.value.get.get.body
} catch {
case ex: Exception =>
Logger.error(ex.toString)
ex.toString
}
}
It is called like this:
def act = Action { request =>
request.body.asFormUrlEncoded match {
case Some(formdata) =>
synchronousPost(url, formdata + ("handshake" -> List("ok"))) match {
Actually it is copy-pasted from some old gist and I am quite sure it can be rewritten in a cleaner way.
How can the line val params = ... be rewritten in a cleaner way? It seems to be low level.
The original formdata comes in from request.body.asFormUrlEncoded and I simply need to append the handshake parameter to the formdata-map and send the original request back to the sender to do the handshake.
Since formdata is a Map[String, Seq[String]], a data type for which a default WSBodyWritable is provided, you can simply use it as the request body directly:
val future: Future[WSResponse] = ws.url(url)
.withHttpHeaders(("Content-Type", "application/x-www-form-urlencoded"))
.post(formdata)
Incidentally, it's considered bad form to use Await when it's easy to make Play controllers return a Future[Result] using Action.async, e.g:
def asyncPost(url: String, formdata: Map[String, Seq[String]]): Future[String] = {
ws.url(url)
.withHttpHeaders(("Content-Type", "application/x-www-form-urlencoded"))
.post(formdata)
.map(_.body)
}
def action = Action.async { request =>
request.body.asFormUrlEncoded match {
case Some(formdata) =>
asyncPost(url, formdata + ("handshake" -> List("ok"))).map { body =>
Ok("Here is the body: " + body)
} recover {
case e =>
Logger.error(e)
InternalServerError(e.getMessage)
}
case None => Future.successful(BadRequest("No form data given"))
}
}

Akka-http logrequest not logging the request body

I am using akka-http and trying to log a request on a specific path using logrequest :
path(Segment / "account") { id =>
logRequest("users/account", Logging.InfoLevel) {
post {
entity(as[Account]) { account => ???
complete(HttpResponse(StatusCodes.NoContent))
}
}
}
however on my log I see something like
HttpRequest(HttpMethod(POST),https://localhost:9009/api/users/123/account,List(Host: localhost:9009, User-Agent: akka-http/10.0.6, Timeout-Access: <function1>),HttpEntity.Chunked(application/json),HttpProtocol(HTTP/1.1))
what I am looking for is the exact request including the body (json) as it was sent by the requestor.
The "HttpEntity.Chunked(application/json)" segment of the log is the output of HttpEntity.Chunked#toString. To get the entire request body, which is implemented as a stream, you need to call HttpEntity#toStrict to convert the Chunked request entity into a Strict request entity. You can make this call in a custom route:
def logRequestEntity(route: Route, level: LogLevel)
(implicit m: Materializer, ex: ExecutionContext) = {
def requestEntityLoggingFunction(loggingAdapter: LoggingAdapter)(req: HttpRequest): Unit = {
val timeout = 900.millis
val bodyAsBytes: Future[ByteString] = req.entity.toStrict(timeout).map(_.data)
val bodyAsString: Future[String] = bodyAsBytes.map(_.utf8String)
bodyAsString.onComplete {
case Success(body) =>
val logMsg = s"$req\nRequest body: $body"
loggingAdapter.log(level, logMsg)
case Failure(t) =>
val logMsg = s"Failed to get the body for: $req"
loggingAdapter.error(t, logMsg)
}
}
DebuggingDirectives.logRequest(LoggingMagnet(requestEntityLoggingFunction(_)))(route)
}
To use the above, pass your route to it:
val loggedRoute = logRequestEntity(route, Logging.InfoLevel)

Scalatra Set Response Headers

I am trying to set response headers during post request. While everything compiles properly, headers are not set.
Here is my code:
post("/get_value"){
val jsonString = request.body;
response.setHeader("Access-Control-Allow-Origin", "*")
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE")
response.setHeader("Access-Control-Max-Age", "3600")
response.setHeader("Access-Control-Allow-Headers", "x-requested-with, content-type")
jsonString
}
What is the valid way to set headers of this kind?
Thanks!
I'm not familiar with Scalatra but you can notice that ActionResult is a case class;
case class ActionResult(status: ResponseStatus, body: Any, headers: Map[String, String])
3rd parameter of this case class is Map[String,String] which should be response header.
There is also;
object Ok {
def apply(body: Any = Unit, headers: Map[String, String] = Map.empty, reason: String = "") = ActionResult(responseStatus(200, reason), body, headers)
}
Returns http response with status code 200, you can create it like;
Ok("response",Map('HeaderKey' -> 'HeaderValue'))
As a conclusion final solution can be like;
post("/get_value") {
val jsonString = request.body;
val headers = Map("Access-Control-Allow-Origin" -> "*",
"Access-Control-Allow-Methods" -> "POST, GET, OPTIONS, DELETE",
"Access-Control-Max-Age" -> "3600",
"Access-Control-Allow-Headers" -> "x-requested-with, content-type")
Ok(jsonString,headers)
}

How to convert from response 'set-cookie' header to request 'cookie' header in spray?

I'm attempting to use spray-client and spray-httpx and I'm having trouble figuring out how to convert 'set-cookie' headers from HttpResponse to a 'cookie' header that I'd like to set on an HttpRequest
val responseSetCookieHeaders = response.headers filter { _.name == "Set-Cookie" }
...
val requestCookieHeader:HttpHeader = ???
...
addHeader(requestCookieHeader) ~> sendReceive ~> { response => ??? }
I do see spray.http.HttpHeaders.Cookie, but I see no way to convert from an instance of HttpHeader to HttpCookie...
HttpHeaders.Cookie is a case class with an unapply method. So you can extract it from response with a simple function:
def getCookie(name: String): HttpHeader => Option[HttpCookie] = {
case Cookie(cookies) => cookies.find(_.name == name)
}
That's a bit more general case, but i think the solution is clear.
I would do this in the following way:
// some example response with cookie
val httpResponse = HttpResponse(headers = List(`Set-Cookie`(HttpCookie("a", "b"))))
// extracting HttpCookie
val httpCookie: List[HttpCookie] = httpResponse.headers.collect { case `Set-Cookie`(hc) => hc }
// adding to client pipeline
val pipeline = addHeader(Cookie(httpCookie)) ~> sendReceive