使用TypeLevel Scala构建HTTP服务时的http4s编译错误排查
解决TypeLevel Scala调用NWS API的编译错误
下面针对你遇到的三个编译错误逐一给出解决方案:
1. URI插值器歧义冲突
错误原因
http4s和sttp两个库都提供了uri字符串插值器,编译器无法确定使用哪一个,导致隐式转换歧义。
解决方案
显式指定使用sttp的URI插值器,避免冲突:
// 替换原uri调用为sttp的显式引用 .get(sttp.client3.UriContext.uri"https://api.weather.gov/points/$latitude,$longitude")
或者在导入时排除http4s的插值器(如果不需要使用http4s的uri语法):
// 不导入http4s的LiteralsSyntax,避免和sttp冲突 import org.http4s._ import org.http4s.dsl.io._ import org.http4s.implicits._ import org.http4s.blaze.server.BlazeServerBuilder import org.http4s.circe._
2. Circe缺少Map[String, Any]的Decoder
错误原因
Circe默认不提供Map[String, Any]的解码器,这类非类型安全的解析方式不符合Circe的设计理念,更推荐用case class解析结构化JSON。
解决方案
定义对应NWS API响应结构的case class,利用circe-generic自动生成类型安全的解码器:
// 新增对应NWS API响应的case class case class NwsPointsResponse(properties: PointsProperties) case class PointsProperties(forecast: String) case class NwsForecastResponse(properties: ForecastProperties) case class ForecastProperties(periods: List[ForecastPeriod]) case class ForecastPeriod(shortForecast: String, temperature: Double)
修改getWeather方法的响应解析逻辑:
def getWeather(latitude: Double, longitude: Double): IO[WeatherResponse] = { val backend = HttpURLConnectionBackend() val request = basicRequest .get(sttp.client3.UriContext.uri"https://api.weather.gov/points/$latitude,$longitude") .response(asJson[NwsPointsResponse]) IO.fromEither(request.send(backend).body) .flatMap { pointsRes => val forecastUrl = pointsRes.properties.forecast val forecastRequest = basicRequest .get(sttp.client3.UriContext.uri"$forecastUrl") .response(asJson[NwsForecastResponse]) IO.fromEither(forecastRequest.send(backend).body) .map { forecastRes => val todayPeriod = forecastRes.properties.periods.head WeatherResponse(todayPeriod.shortForecast, classifyTemperature(todayPeriod.temperature)) } } }
3. WeatherResponse缺少EntityEncoder
错误原因
http4s需要将WeatherResponse序列化为HTTP响应实体,但默认没有对应的EntityEncoder,需要将Circe的Encoder转换为http4s的EntityEncoder。
解决方案
导入http4s-circe的EntityEncoder扩展:
// 在原导入基础上添加这一行 import org.http4s.circe.CirceEntityEncoder._
修改后的完整代码
package weather import cats.effect._ import org.http4s._ import org.http4s.dsl.io._ import org.http4s.implicits._ import org.http4s.blaze.server.BlazeServerBuilder import org.http4s.circe._ import org.http4s.circe.CirceEntityEncoder._ import io.circe.generic.auto._ import sttp.client3._ import sttp.client3.circe._ object WeatherServer extends IOApp { case class WeatherResponse(forecast: String, temperatureType: String) // NWS API响应对应的case class case class NwsPointsResponse(properties: PointsProperties) case class PointsProperties(forecast: String) case class NwsForecastResponse(properties: ForecastProperties) case class ForecastProperties(periods: List[ForecastPeriod]) case class ForecastPeriod(shortForecast: String, temperature: Double) def classifyTemperature(temp: Double): String = { if (temp < 10) "cold" else if (temp > 25) "hot" else "moderate" } def getWeather(latitude: Double, longitude: Double): IO[WeatherResponse] = { val backend = HttpURLConnectionBackend() val request = basicRequest .get(sttp.client3.UriContext.uri"https://api.weather.gov/points/$latitude,$longitude") .response(asJson[NwsPointsResponse]) IO.fromEither(request.send(backend).body) .flatMap { pointsRes => val forecastUrl = pointsRes.properties.forecast val forecastRequest = basicRequest .get(sttp.client3.UriContext.uri"$forecastUrl") .response(asJson[NwsForecastResponse]) IO.fromEither(forecastRequest.send(backend).body) .map { forecastRes => val todayPeriod = forecastRes.properties.periods.head WeatherResponse(todayPeriod.shortForecast, classifyTemperature(todayPeriod.temperature)) } } } val weatherService = HttpRoutes.of[IO] { case GET -> Root / "weather" :? LatitudeQueryParamMatcher(lat) +& LongitudeQueryParamMatcher(lon) => getWeather(lat, lon).flatMap { weather => Ok(weather) } } val httpApp = weatherService.orNotFound def run(args: List[String]): IO[ExitCode] = BlazeServerBuilder[IO](runtime.compute) .bindHttp(8080, "0.0.0.0") .withHttpApp(httpApp) .serve .compile .drain .as(ExitCode.Success) } object LatitudeQueryParamMatcher extends QueryParamDecoderMatcher[Double]("latitude") object LongitudeQueryParamMatcher extends QueryParamDecoderMatcher[Double]("longitude")
内容的提问来源于stack exchange,提问作者PacificNW_Lover
相关产品推荐
相关产品推荐

