1

Here is some bash code that works:

COOKIE_FILE=cookies.txt
DOMAIN="http://localhost:9000"

function login {
  URL_LOGIN="$DOMAIN/asfd/asdf"

  rm -f "$COOKIE_FILE"
  curl -s \
    -H "Content-type: application/x-www-form-urlencoded" \
    -c "$COOKIE_FILE" \
    -d "username=asdf" \
    -d "password=asdf" \
    -L "$URL_LOGIN" > /dev/null
  if [ -f "$COOKIE_FILE" ]; then
    GREP="grep -P"
    if [ $OSTYPE == *darwin* ]; then GREP=grep; fi
    COOKIE=`$GREP "\tid\t" "$COOKIE_FILE"`
    if [ -z "$COOKIE" ]; then
      echo "Login failed; check your userid and password"
      exit -2
    else
      echo "Cookie is: $COOKIE"
    fi
  else
    echo "Problem: $COOKIE_FILE does not exist"
    exit -3
  fi
}

This is my attempt at similar code with Scala 2.13 running on JVM 11 (non-Scala programmers should easily read this by squinting slightly).

trait HTTPLike {
  def ofFormData(data: immutable.Map[String, String]): HttpRequest.BodyPublisher = {
    val builder = new StringBuilder
    data.foreach { entry =>
      if (builder.nonEmpty) builder.append("&")
      builder.append(URLEncoder.encode(entry._1.toString, StandardCharsets.UTF_8))
      builder.append("=")
      builder.append(URLEncoder.encode(entry._2.toString, StandardCharsets.UTF_8))
    }
    BodyPublishers.ofString(builder.toString)
  }
}

trait HTTPLike extends HTTPLike {
  import java.net.http.HttpClient.Redirect
  import java.net.http.HttpResponse.BodyHandlers
  import java.net.http.{HttpClient, HttpRequest, HttpResponse}
  import java.net.{CookieHandler, URI}
  import scala.collection.immutable
  import scala.jdk.OptionConverters._

  private val domainStr = "http://localhost:9000"

  private val uriLogin: URI = URI.create(s"$domainStr/asdf/asdf")

  CookieHandler.setDefault(new CookieManager)
  CookieHandler.getDefault.asInstanceOf[CookieManager]
    .setCookiePolicy(CookiePolicy.ACCEPT_ALL)

  private val client: HttpClient = HttpClient.newBuilder
    .cookieHandler(CookieHandler.getDefault)
    .followRedirects(Redirect.NORMAL)
    .build

  private val loginData: Map[String, String] = immutable.Map("username" -> "asdf", "password" -> "asdf")

  private val requestLogin: HttpRequest = HttpRequest.newBuilder
    .uri(uriLogin)
    .header("Content-Type", "application/x-www-form-urlencoded")
    .POST(ofFormData(loginData))
    .build

  def maybeLoginCookie: Option[String] = {
    val loginResponse: HttpResponse[String] = client.send(requestLogin, BodyHandlers.ofString)
    val idCookie: Option[String] = loginResponse.headers.firstValue("id").toScala
    idCookie
  }
}

maybeLoginCookie is called from somewhere else.

I don't get a cookie back, even though I see that authentication worked. Might the problem be something about how I initialized the HTTP client's cookie handling?

Mike Slinn
  • 7,705
  • 5
  • 51
  • 85
  • I'd suggest enabling logging of request/response to figure out what is sent by the client and returned by the server. https://stackoverflow.com/questions/53215038/how-to-log-request-response-using-java-net-http-httpclient/53231046#53231046 – daniel Jul 08 '19 at 12:39
  • Sounds like a good idea. Thanks for the link – Mike Slinn Jul 08 '19 at 16:04
  • After setting `-Djdk.httpclient.HttpClient.log=errors,requests,headers,content,trace,channel` I see the cookie is received, but seems to be ignored. My setup for cookie handling is the problem... – Mike Slinn Jul 08 '19 at 16:50
  • I have set `CookieHandler.getDefault.asInstanceOf[CookieManager].setCookiePolicy(CookiePolicy.ACCEPT_ALL)`. Stepping through Java 11's `CookieFilter.java`, I see the incoming `set-cookie` header with the desired cookie. `CookieManager.java` stores the cookie in the cookie jar with `put()`. My desired cookie is `Secure; HTTPOnly`, I wonder if that means it needs to be specially handled? – Mike Slinn Jul 08 '19 at 17:12
  • `CookieFilter.java` puts the desired cookie in `cookieHandler` and the cookie is removed from the request header, so my code is looking for the cookie in the wrong place. My code can access `cookieHandler` like this: `client.cookieHandler` but it is unclear how to retrieve the value of the desired cookie inside. – Mike Slinn Jul 08 '19 at 17:25
  • To summarize, my app now works because `CookiePolicy.ACCEPT_ALL` causes the cookie to be set. My only concern is that I am currently unaware of a way of discovering if the cookie is set so error handling is not possible. – Mike Slinn Jul 08 '19 at 17:59
  • You can always provide your own implementation of CookieHandler. As a side not you also don't need to set it as default: it should be enough to pass it to the HttpClient builder. – daniel Jul 10 '19 at 09:46
  • I just want to know if a cookie with a given name was received. I don't need to do anything with it. Would this require a custom cookie handler, and if so, where is that documented and is there a sample? – Mike Slinn Jul 10 '19 at 16:16
  • 1
    You could try to call `CookieHandler::get` on the `CookieHandler` you passed to the `HttpClient` builder to figure that out. Or you could examine the HTTP response headers yourself when you receive the response. Look for `set-cookie`/`set-cookie2`. – daniel Jul 10 '19 at 16:36

0 Answers0