繁体   English   中英

什么相当于使用 Python 的 Asyncio 在 Scala 中生成 HTTP 请求?

[英]What is equivalent of using Python's Asyncio for generating HTTP requests in Scala?

我目前正在尝试使用Scala构建异步客户端数据获取器(通过 HTTP)。 获取的数据将被解析,并插入到数据库中,但这超出了这个问题的 scope。

主要目标是:

  1. 构建一个 function 以异步从 api 中提取数据。
  2. 控制请求频率(换句话说:可以灵活地处理速率限制)。

为了更好地了解我正在尝试做的事情,我在 Python 中编写了一些代码来帮助更好地表达我的问题。

我首先定义一个 function 来创建异步 http 请求(我使用aiohttp因为我发现它表达了我的意图):

from aiohttp import ClientResponse, ClientSession, ClientError, http_exceptions 


async def fetch_data(url: str, method: str, session: ClientSession, **kwargs) -> dict:
    """
    Request wrapper that fetches data. Passes kwargs to session.
    Args:
        :param url: api endpoint used (eg. https://foo.bar.com/api/v3/baz)
        :param method: HTTP method used (eg. GET, POST...etc.)
        :param session: aiohttp.ClientSession object. The main entry point for all client API operations.
    Kwargs:
        ___ : Includes other KWARGS that may be passed to a session (eg. Headers, SSL...etc.)
    Returns:
        Dict
     """
    response: ClientResponse = await session.request(url=url, method=method, **kwargs)
    response.raise_for_status()  # Raises an aiohttp.ClientResponseError if the response status is 400 or higher
    logger.info("Got response [%s] for request: %s", response.status, url)
    data: dict = await response.json()
    return data

稍后我会使用这个 function 来检索数据并使用它进行实际工作(注意 TCPConnector 的限制,它限制了同时连接的总数):

H = {"Authorization": "orgId={org_id}".format(org_id=config.account_id())}
C = config.cert_dir()

async def main():
    sslcontext = ssl.create_default_context(cafile=None)
    sslcontext.load_cert_chain(certfile=C + "Admin_API_access.pem", keyfile=C + "Admin_API_access.key")
    connector = aiohttp.TCPConnector(limit=0, ssl=sslcontext)  # limit=0 can be used to reduce the number of async requests

    async with aiohttp.ClientSession(connector=connector) as context:
        # Call API and do something with the data (eg. write to a file)

根据我的理解(也让我感到困惑),有几种方法可以在 Scala 中完成此操作。什么是上述 Scala 中的 Python 代码的等价物,可以在不阻塞的情况下生成 HTTP 请求。 由于 Asyncio 是单线程的,我并不是特别在寻找单线程解决方案,而是寻找异步解决方案。

最后,我不是在找人为我编写代码,而是在寻求对我可以 go 提供相同功能的潜在解决方案的概念性理解。

从根本上说,在 Scala 中,异步由Future表示,可以将其视为单个 object 的包装器:

  • 可能在某个时间点(可能是过去)完成
  • 如果它已完成,则它要么以 object 完成,要么出现异常

可以注册回调函数,它会在Future完成后的某个时刻对它进行操作; 也可以以一种非常类似于集合的方式处理Future ,使用mapflatMap等来转换它(在幕后,这些操作注册一个回调并返回一个由回调完成的新Future :使用mapflatMap , recover and friends 通常比手动回调注册更可取)。

这是一个非常通用的 API 并且有很多很多方法可以实现它。 一般来说,如果一个 Scala 库返回Future s,它支持开箱即用的异步操作。 不乏会产生 HTTP 请求的Future的库,包括但绝对不限于:

  • Akka HTTP
  • 派遣
  • 千兆马
  • 玩WS

选择这样一个库基本上是一个品味问题:正在使用的其他库可能会引导您找到一个特定的库(例如,如果您正在使用其他 Akka 库,使用 Akka HTTP 可能是有意义的),您可能会找到一个库或另一个更好地支持特定的用例。

另外,如果一个特定的库没有给你一个Future ,你可以很容易地包装它:

import some.blocking.http.request.library

import scala.concurrent.ExecutionContext

def asyncRequestUrl(url: String)(implicit ectx: ExecutionContext): Future[Response] =
  Future {
    library.request(url)
  }

scala.concurrent.ExecutionContext表示一个线程池,其中积压了要在池中执行的任务。 Future { code... } (技术上Future.apply )添加一个任务来执行code...并使用执行结果完成未来(立即返回)。 请注意,由于线程池中的线程数几乎总是有上限,因此只要池中的所有线程都忙于其他任务,任务就会一直处于“已调度但未执行”状态 state。

如果调用者不提供ExecutionContext ,编译器会将此标记为错误,并可能建议使用scala.concurrent.ExecutionContext.Implicits.global 在开发/测试环境中使用此ExecutionContext可能没问题,但几乎肯定会希望创建一个线程数多于默认全局上下文的自定义ExecutionContext ,至少要发出 HTTP 次请求。 使用像我上面列出的异步设计库在选择合适的线程池方面可能会“正常工作”(如果发现它不“正常工作”,则可能是可配置的)。

如果一个人在面向 FP 的代码库中,可能会想要使用类似http4s的东西,它使用与“vanilla”Scala 略有不同的 model 来实现异步(例如 Cats Effect 的ConcurrentEffect )。 如果愿意,有多种方法可以使其与 vanilla Scala 一起使用。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM