Соединение Http4s отклонено при запросе с параметрами - PullRequest
0 голосов
/ 10 июля 2019

Я отправляю запрос к конечной точке API на сервере API, который я построил с использованием HTTP4. Пока я не включаю параметр запроса, все работает. Когда я пытаюсь включить параметр запроса, я получаю эту ошибку:

Исключение в потоке "main" java.io.IOException: удаленный компьютер отказал в подключении к сети.

Я создаю многоуровневое приложение, которое будет иметь веб-интерфейс. В конечном итоге мы намерены Dockerize API-сервер. Сервер API обеспечивает связь с Dockerized-экземпляром MySQL, работающим на сервере. В настоящее время все компоненты работают на моем компьютере локально, и я стремлюсь дать возможность пользователю загружать файл Excel через веб-браузер и переводить его содержимое в базу данных MySQL на внутреннем сервере.

Из-за модели данных MySQL мне нужно извлечь идентификатор эксперимента из базы данных после его вставки, чтобы я мог передать его каждой записи. Идентификатор эксперимента - это внешний ключ для записей из эксперимента, данные которых хранятся в одном файле. Экспериментальная сущность создается в MySQL и ей присваивается автоматически увеличивающийся идентификатор, затем записи этого эксперимента загружаются по одной в базу данных, и им требуется идентификатор для связи с родительским экспериментом.

Вот клиент, который отправляет данные на сервер API

import cats.effect.{ContextShift, IO, Resource, Timer}
import io.circe.Json
import models.ExperimentData
import io.circe.generic.auto._
import org.http4s.{Method, Request, Uri}
import org.http4s.client.blaze.BlazeClientBuilder
import scala.concurrent.ExecutionContext.global
import org.http4s.client.Client
import io.circe.syntax._
import org.http4s.EntityDecoder
import io.circe.literal._
import org.http4s._
import org.http4s.circe._

import scala.concurrent.duration.Duration

/**
  * This class sends requests to the API server.
  */
object ApiClient {
  implicit val cs: ContextShift[IO] = IO.contextShift(global)
  implicit val timer: Timer[IO] = IO.timer(global)
  case class ExperimentId(ExperimentId: Int)
  implicit val expIdDecoder: EntityDecoder[IO, ExperimentId] = jsonOf[IO, ExperimentId]

  def createClient: Resource[IO, Client[IO]] = BlazeClientBuilder[IO](global)
    .withResponseHeaderTimeout(Duration.Inf)
    .withIdleTimeout(Duration.Inf)
    .withRequestTimeout(Duration.Inf)
    .withMaxWaitQueueLimit(Int.MaxValue)
    .withMaxConnectionsPerRequestKey(Function.const(Int.MaxValue))
    .resource

  def sendGetExperimentIdFromFilenameRequest(filename: String): IO[ExperimentId] = createClient.use(client => {
    val uriString = s"http://localhost:8081/experiment/id"
    println(s"uriString = $uriString")
    val theUri: Uri = Uri(path = uriString, query = Query(("filename", Some(filename))))
    println(s"theUri = $theUri")
    val contentHeader: Header = Header("Content-type", "application/json")
    val acceptHeader: Header = Header("Accept", "application/json")
    val req: Request[IO] = Request(method = Method.GET, uri = theUri)
      .withHeaders(contentHeader, acceptHeader)
      .withEntity(filename.asJson)
    client.expect[Json](req).flatMap(x => IO(x.as[ExperimentId].getOrElse(ExperimentId(-1))))
  })
}

Вот служба, к которой она должна обратиться:

import clients.MySqlClient
import io.circe.generic.auto._
import cats.effect._
import io.circe.literal._
import org.http4s.circe._
import models.{ExperimentData, Metadata}
import models.RawData.RawDataRecord
import org.http4s.dsl.io._
import org.http4s.{EntityDecoder, HttpRoutes, Response}

import scala.concurrent.ExecutionContext

object MySqlRoutingService {
  def apply(mySqlClient: MySqlClient)(implicit ec: ExecutionContext): HttpRoutes[IO] =
    new MySqlRoutingService(mySqlClient).serve()
}

final class MySqlRoutingService(mySqlClient: MySqlClient)(implicit ec: ExecutionContext) {
  private implicit val cs: ContextShift[IO] = IO.contextShift(ec)
  implicit val userDecoder: EntityDecoder[IO, ExperimentData] = jsonOf[IO, ExperimentData]
  implicit val recordDecoder: EntityDecoder[IO, RawDataRecord] = jsonOf[IO, RawDataRecord]
  implicit val metadataDecoder: EntityDecoder[IO, Metadata] = jsonOf[IO, Metadata]
  object FilenameQueryParamMatcher extends QueryParamDecoderMatcher[String]("filename")

  def serve(): HttpRoutes[IO] = HttpRoutes.of[IO] {
    case req@POST -> Root => req.as[ExperimentData].flatMap(uploadExperimentToDatabase)
    case req@POST -> Root / "RawData" / IntVar(expId) =>
      req.as[RawDataRecord].flatMap(record => uploadRecordToDatabase(expId, record))
    case req@POST -> Root / "CompoundTest" => req.as[Metadata].flatMap(_ => Ok())
    case GET -> Root => Ok(json"""{"Message": "Successfully uploaded to database"}""")
    case GET -> Root / "id" :? FilenameQueryParamMatcher(filename) => {
      println("Hit the end point.")
      getExperimentIdFromFilename(filename = filename)
    }
  }

  def getExperimentIdFromFilename(filename: String): IO[Response[IO]] =
    mySqlClient.getExperimentIdFromFilename(filename)
      .flatMap(expId => Ok(json"""{"ExperimentId" : $expId}"""))
}

Когда я пытаюсь отправить запрос к конечной точке API на http://localhost:8081/experiment/id и дать ему параметр запроса, как вы можете видеть в функции sendGetExperimentIdFromFilenameRequest, я получаю вышеупомянутую ошибку «отказано в соединении». Если это помогает, выходные данные моих printlns в sendGetExperimentIdFromFilenameRequest:

uriString = http://localhost:8081/experiment/id theUri = http://localhost:8081/experiment/id?filename=

...