All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.lolgab.httpclient.internal.CurlImpl.scala Maven / Gradle / Ivy

package com.github.lolgab.httpclient.internal

import scala.scalanative.unsafe._
import scala.scalanative.unsigned._
import scala.collection.mutable
import scala.concurrent.duration._
import scala.scalanative.libc.stdlib._
import scala.scalanative.libc.string._
import scala.scalanative.runtime.{
  ByteArray,
  Intrinsics,
  LongArray,
  MemoryPool,
  fromRawPtr,
  toRawPtr
}
import scala.scalanative.loop._
import scala.scalanative.loop.LibUV._
import scala.scalanative.loop.LibUVConstants._
import com.github.lolgab.httpclient._
import scala.annotation.tailrec

private[httpclient] object CurlImpl {
  import CApi._
  import CApiOps._

  if (curl_global_init(CURL_GLOBAL_ALL) != 0) {
    throw new Error("Could not init curl")
  }

  private val timerMemory = new Array[Byte](uv_handle_size(UV_TIMER_T).toInt)
  val timerHandle = timerMemory.asInstanceOf[ByteArray].at(0)
  uv_timer_init(EventLoop.loop, timerHandle)

  val curlHandle = curl_multi_init()

  private def curlPerform(rwResult: RWResult, sockfd: Int) = {
    val runningHandles = stackalloc[CInt]()
    var flags = 0

    if (rwResult.readable)
      flags |= CURL_CSELECT_IN;
    if (rwResult.writable)
      flags |= CURL_CSELECT_OUT;

    curl_multi_socket_action(
      curlHandle,
      sockfd,
      flags,
      runningHandles
    )

    checkMultiInfo()
  }

  class CurlContext(val poll: Poll, val sockfd: Int)
  object CurlContext {
    private val contexts = mutable.Set[CurlContext]()
    def apply(sockfd: Int): CurlContext = {
      val res = new CurlContext(Poll(sockfd), sockfd)
      // Hold the reference to avoid garbage collection
      contexts += res
      res
    }
    def unref(context: CurlContext) = contexts -= context
  }

  val handleSocketCFuncPtr: CurlSocketCallback = (
      easy: Ptr[Byte],
      sockfd: Int,
      action: Int,
      userp: Ptr[Byte],
      socketp: Ptr[Byte]
  ) => {
    val context =
      Intrinsics.castRawPtrToObject(toRawPtr(socketp)).asInstanceOf[CurlContext]
    handleSocket(easy, sockfd, action, context)
  }

  def handleSocket(
      easy: Ptr[Byte],
      sockfd: Int,
      action: Int,
      socketp: CurlContext
  ): Int = {
    action match {
      case CURL_POLL_IN | CURL_POLL_OUT | CURL_POLL_INOUT =>
        val curlContext = if (socketp == null) {
          CurlContext(sockfd)
        } else {
          socketp
        }
        curl_multi_assign(
          curlHandle,
          sockfd,
          fromRawPtr[Byte](Intrinsics.castObjectToRawPtr(curlContext))
        )
        curlContext.poll
          .start((action & CURL_POLL_IN) != 0, (action & CURL_POLL_OUT) != 0) {
            rwResult => curlPerform(rwResult, sockfd)
          }
      case CURL_POLL_REMOVE =>
        if (socketp != null) {
          socketp.poll.stop()
          CurlContext.unref(socketp)
          curl_multi_assign(curlHandle, sockfd, null)
        }
      case other =>
        throw new java.lang.RuntimeException(
          s"Action code not supported $other"
        )
    }
    0
  }
  curl_multi_setopt(curlHandle, CURLMOPT_SOCKETFUNCTION, handleSocketCFuncPtr)
  val startTimeout: CurlTimerCallback =
    (multi: Ptr[Byte], timeout_ms: CLong, userp: Ptr[Byte]) => {
      if (timeout_ms < 0) uv_timer_stop(timerHandle)
      else {
        val newTimeout = if (timeout_ms == 0) 1 else timeout_ms.toLong
        uv_timer_start(
          timerHandle,
          (_: Ptr[Byte]) => {
            val running_handles = stackalloc[CInt]()
            curl_multi_socket_action(
              curlHandle,
              CURL_SOCKET_TIMEOUT,
              0,
              running_handles
            )
            checkMultiInfo()
          },
          newTimeout,
          0
        )
      }
      0
    }
  curl_multi_setopt(curlHandle, CURLMOPT_TIMERFUNCTION, startTimeout)

  val writeMemoryCallback: CurlDataCallback =
    (ptr: Ptr[Byte], size: CSize, nmemb: CSize, data: Ptr[CurlBuffer]) => {
      val index: CSize = (!data)._2
      val increment: CSize = size * nmemb
      (!data)._2 = (!data)._2 + increment
      (!data)._1 = realloc((!data)._1, (!data)._2 + 1.toUInt)
      memcpy((!data)._1 + index, ptr, increment)
      !(!data)._1.+((!data)._2) = 0.toByte
      size * nmemb
    }

  type Memory = CStruct2[Ptr[Byte], CSize]
  def checkMultiInfo(): Unit = {
    @tailrec
    def loop(): Unit = {
      val pending = stackalloc[CInt]()
      val message = curl_multi_info_read(curlHandle, pending)
      if (message != null) {
        if (message.msg == CURLMSG_DONE) {
          val easyHandle = message.easy_handle
          val responseCode = stackalloc[CLong]()
          curl_easy_getinfo(
            easyHandle,
            CURLINFO_RESPONSE_CODE,
            responseCode.asInstanceOf[Ptr[Byte]]
          )
          val request = HandleUtils.getData[Request](easyHandle)
          val response = Response(
            code = (!responseCode).toInt,
            body = StringUtils
              .fromCStringAndSize(request.memory._1, request.memory._2.toInt)
          )
          try {
            request.callback(response)
          } finally {
            free(request.memory._1)
            free(request.memory.asInstanceOf[Ptr[Byte]])
            curl_slist_free_all(request.headersList)
            curl_multi_remove_handle(curlHandle, easyHandle)
            curl_easy_cleanup(easyHandle)
            HandleUtils.unref(request)
          }
        }
        loop()
      }
    }
    loop()
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy