aboutsummaryrefslogtreecommitdiff
path: root/client
diff options
context:
space:
mode:
authorJakob Odersky <jakob@inpher.io>2019-10-09 17:10:43 -0400
committerJakob Odersky <jakob@inpher.io>2019-10-09 20:33:16 -0400
commit0ceee5ed4bae240b8c8e94d2fd7424d9d0b67ec7 (patch)
tree2df0258f81050e6fed51d38e217c4f6256518e12 /client
parentfaed28c54900fc0b359700873367095f51425794 (diff)
downloadscala-triad-0ceee5ed4bae240b8c8e94d2fd7424d9d0b67ec7.tar.gz
scala-triad-0ceee5ed4bae240b8c8e94d2fd7424d9d0b67ec7.tar.bz2
scala-triad-0ceee5ed4bae240b8c8e94d2fd7424d9d0b67ec7.zip
Migrate build to mill
Diffstat (limited to 'client')
-rw-r--r--client/src/Main.scala (renamed from client/src/main/scala/Main.scala)0
-rw-r--r--client/src/http/ArrayUtils.scala28
-rw-r--r--client/src/http/CurlBackend.scala215
-rw-r--r--client/src/http/curl.scala82
-rw-r--r--client/src/http/package.scala3
5 files changed, 328 insertions, 0 deletions
diff --git a/client/src/main/scala/Main.scala b/client/src/Main.scala
index 4b11fe0..4b11fe0 100644
--- a/client/src/main/scala/Main.scala
+++ b/client/src/Main.scala
diff --git a/client/src/http/ArrayUtils.scala b/client/src/http/ArrayUtils.scala
new file mode 100644
index 0000000..00c1067
--- /dev/null
+++ b/client/src/http/ArrayUtils.scala
@@ -0,0 +1,28 @@
+package triad
+package http
+
+import scala.scalanative.native._
+
+object ArrayUtils {
+
+ def toBuffer(array: Array[Byte])(implicit z: Zone): Ptr[Byte] = {
+ val buffer = z.alloc(array.size)
+ var i = 0
+ while (i < array.size) {
+ buffer(i) = array(i)
+ i += 1
+ }
+ buffer
+ }
+
+ def toArray(buffer: Ptr[Byte], size: CSize): Array[Byte] = {
+ val array = new Array[Byte](size.toInt)
+ var i = 0
+ while (i < array.size) {
+ array(i) = buffer(i)
+ i += 1
+ }
+ array
+ }
+
+}
diff --git a/client/src/http/CurlBackend.scala b/client/src/http/CurlBackend.scala
new file mode 100644
index 0000000..4dc8577
--- /dev/null
+++ b/client/src/http/CurlBackend.scala
@@ -0,0 +1,215 @@
+package triad
+package http
+
+import curl._
+import curlh._
+
+import scala.collection.{Map, mutable}
+import scala.collection.mutable.ArrayBuffer
+import scala.concurrent.Future
+import scala.scalanative.native._
+import scala.util.{Failure, Success, Try}
+
+object CurlBackend {
+
+ type Chunk = CStruct4[Ptr[CStruct0], Ptr[CStruct0], CSize, Ptr[Byte]]
+ implicit class ChunksOps(val self: Ptr[Chunk]) extends AnyVal {
+ @inline def prev: Ptr[Chunk] = (!self._1).cast[Ptr[Chunk]]
+ @inline def prev_=(other: Ptr[Chunk]): Unit =
+ !(self._1) = other.cast[Ptr[CStruct0]]
+ @inline def next: Ptr[Chunk] = (!self._2).cast[Ptr[Chunk]]
+ @inline def next_=(other: Ptr[Chunk]): Unit =
+ !(self._2) = other.cast[Ptr[CStruct0]]
+ @inline def size: CSize = !(self._3)
+ @inline def size_=(value: CSize): Unit = !(self._3) = value
+ @inline def buffer: Ptr[Byte] = !(self._4)
+ @inline def buffer_=(value: Ptr[Byte]): Unit = !(self._4) = value
+ }
+
+ object Chunk {
+
+ @inline def NullPtr[T] = 0.cast[Ptr[T]]
+
+ def allocHead() = allocAppend(0, NullPtr[Chunk])
+
+ def allocAppend(size: CSize,
+ head: Ptr[Chunk] = NullPtr[Chunk]): Ptr[Chunk] = {
+ val chunk: Ptr[Chunk] = stdlib.malloc(sizeof[Chunk]).cast[Ptr[Chunk]]
+ if (chunk == NullPtr[Chunk]) return NullPtr[Chunk]
+
+ chunk.buffer = stdlib.malloc(size)
+
+ if (chunk.buffer == NullPtr[Chunk] && size != 0) {
+ stdlib.free(chunk.cast[Ptr[Byte]])
+ return NullPtr[Chunk]
+ }
+ chunk.size = size
+
+ if (head == NullPtr[Chunk]) { // this will be the head
+ chunk.next = chunk
+ chunk.prev = chunk
+ } else {
+ val last = head.prev
+ last.next = chunk
+ chunk.prev = last
+ head.prev = chunk
+ chunk.next = head
+ }
+ chunk
+ }
+
+ def freeAll(head: Ptr[Chunk]): Unit = {
+ var chunk: Ptr[Chunk] = head
+ do {
+ val next = chunk.next
+ stdlib.free(chunk.buffer)
+ stdlib.free(chunk.cast[Ptr[Byte]])
+ chunk = next
+ } while (chunk != head)
+ }
+
+ def toArray(head: Ptr[Chunk]): Array[Byte] = {
+ val buffer = new ArrayBuffer[Byte]()
+ var chunk = head
+ do {
+ val next = chunk.next
+ var i = 0l
+ while (i < next.size) {
+ buffer += next.buffer(i)
+ i += 1
+ }
+ chunk = next
+ } while (chunk != head)
+ buffer.toArray
+ }
+
+ def traverse(head: Ptr[Chunk])(fct: Array[Byte] => Unit) = {
+ var chunk = head
+ do {
+ val next = chunk.next
+ val buffer = new ArrayBuffer[Byte]()
+ var i = 0l
+ while (i < next.size) {
+ buffer += next.buffer(i)
+ i += 1
+ }
+ chunk = next
+ fct(buffer.toArray)
+ } while (chunk != head)
+ }
+
+ }
+
+ private def receive(data: Ptr[Byte],
+ size: CSize,
+ nmemb: CSize,
+ userdata: Ptr[Byte]): CSize = {
+ val head = userdata.cast[Ptr[Chunk]]
+ val length = size * nmemb
+ val chunk = Chunk.allocAppend(length, head)
+ string.memcpy(chunk.buffer, data, chunk.size)
+ chunk.size
+ }
+ private val receivePtr: WriteFunction = CFunctionPtr.fromFunction4(receive)
+
+ private def chain[A](success: A)(calls: (() => A)*) = {
+ var result: A = success
+ for (c <- calls if result == success) {
+ result = c()
+ }
+ result
+ }
+
+ private def request(request: Request)(implicit z: Zone): Try[Response] = {
+ val curl: CURL = curl_easy_init()
+ if (curl != null) {
+ val errorBuffer = stackalloc[Byte](CURL_ERROR_SIZE)
+ !errorBuffer = 0
+ val requestHeaders = stackalloc[curl_slist](1)
+ !requestHeaders = 0.cast[curl_slist]
+
+ val responseChunks = Chunk.allocHead()
+ val responseHeaderChunks = Chunk.allocHead()
+
+ val curlResult = chain(CURLcode.CURL_OK)(
+ () =>
+ curl_easy_setopt(curl, CURLoption.CURLOPT_ERRORBUFFER, errorBuffer),
+ () =>
+ curl_easy_setopt(curl,
+ CURLoption.CURLOPT_CUSTOMREQUEST,
+ toCString(request.method)),
+ () =>
+ curl_easy_setopt(curl,
+ CURLoption.CURLOPT_URL,
+ toCString(request.url)),
+ () => {
+ val buffer = ArrayUtils.toBuffer(request.body)
+ curl_easy_setopt(curl, CURLoption.CURLOPT_POSTFIELDS, buffer)
+ curl_easy_setopt(curl,
+ CURLoption.CURLOPT_POSTFIELDSIZE,
+ request.body.size)
+ },
+ () => {
+ for ((k, v) <- request.headers) {
+ !requestHeaders =
+ curl_slist_append(!requestHeaders, toCString(s"$k:$v"))
+ }
+ curl_easy_setopt(curl, CURLoption.CURLOPT_HTTPHEADER, !requestHeaders)
+ },
+ () =>
+ curl_easy_setopt(curl, CURLoption.CURLOPT_WRITEFUNCTION, receivePtr),
+ () =>
+ curl_easy_setopt(curl, CURLoption.CURLOPT_WRITEDATA, responseChunks),
+ () =>
+ curl_easy_setopt(curl,
+ CURLoption.CURLOPT_HEADERDATA,
+ responseHeaderChunks),
+ () => curl_easy_perform(curl)
+ )
+
+ val result = curlResult match {
+ case CURLcode.CURL_OK =>
+ val responseCode: Ptr[Long] = stackalloc[Long](1)
+ curl_easy_getinfo(curl, CURLINFO.CURLINFO_RESPONSE_CODE, responseCode)
+
+ val responseHeaders = mutable.HashMap.empty[String, String]
+ Chunk.traverse(responseHeaderChunks) { headerChunk =>
+ val line = new String(headerChunk, "utf-8").trim
+ if (line.contains(":")) {
+ val parts = line.split(":", 2)
+ responseHeaders += parts(0) -> parts(1)
+ }
+ }
+
+ Success(
+ Response(
+ statusCode = (!responseCode).toInt,
+ headers = responseHeaders.toMap,
+ body = Chunk.toArray(responseChunks)
+ ))
+
+ case code =>
+ val message = curl_easy_strerror(curl, code)
+ Failure(
+ new RuntimeException(
+ s"${fromCString(errorBuffer)} (curl exit status $code)"))
+ }
+ Chunk.freeAll(responseChunks)
+ Chunk.freeAll(responseHeaderChunks)
+ curl_slist_free_all(!requestHeaders)
+ curl_easy_cleanup(curl)
+ result
+ } else {
+ Failure(new RuntimeException(s"curl failed to initialize"))
+ }
+ }
+
+ def curlVersion = fromCString(curl_version())
+
+}
+
+trait CurlBackend extends Backend {
+ def send(req: Request): Future[Response] = Zone { implicit z =>
+ Future.fromTry(CurlBackend.request(req))
+ }
+}
diff --git a/client/src/http/curl.scala b/client/src/http/curl.scala
new file mode 100644
index 0000000..1eda584
--- /dev/null
+++ b/client/src/http/curl.scala
@@ -0,0 +1,82 @@
+package triad
+package http
+
+import scala.scalanative.native._
+
+object curlh {
+ final val CURL_ERROR_SIZE = 256
+
+ type CURL = Ptr[CStruct0]
+
+ /*
+ * #define CURLOPTTYPE_LONG 0
+ * #define CURLOPTTYPE_OBJECTPOINT 10000
+ * #define CURLOPTTYPE_STRINGPOINT 10000
+ * #define CURLOPTTYPE_FUNCTIONPOINT 20000
+ * #define CURLOPTTYPE_OFF_T 30000
+ */
+
+ type CURLcode = CInt
+ object CURLcode {
+ final val CURL_OK: CInt = 0
+ }
+
+ type CURLoption = CInt
+ object CURLoption {
+ final val CURLOPT_VERBOSE: CInt = 41
+ final val CURLOPT_POSTFIELDSIZE: CInt = 60
+ final val CURLOPT_WRITEDATA: CInt = 10001
+ final val CURLOPT_URL: CInt = 10002
+ final val CURLOPT_ERRORBUFFER: CInt = 10010
+ final val CURLOPT_POSTFIELDS: CInt = 10015
+ final val CURLOPT_HTTPHEADER: CInt = 10023
+ final val CURLOPT_HEADERDATA: CInt = 10029
+ final val CURLOPT_CUSTOMREQUEST: CInt = 10036
+ final val CURLOPT_WRITEFUNCTION: CInt = 20011
+ }
+
+ type CURLINFO = CInt
+ object CURLINFO {
+ final val CURLINFO_RESPONSE_CODE = 0x200002
+ }
+
+ type WriteFunction = CFunctionPtr4[
+ Ptr[Byte], // data
+ CSize, // size
+ CSize, // nmemb
+ Ptr[Byte], // userdata
+ CSize // return
+ ]
+
+ type curl_slist = Ptr[CStruct0]
+
+}
+
+@link("curl")
+@extern
+object curl {
+ import curlh._
+
+ def curl_easy_init(): CURL = extern
+
+ def curl_easy_setopt(curl: CURL,
+ option: CURLoption,
+ parameter: CVararg*): CURLcode = extern
+
+ def curl_easy_perform(curl: CURL): CURLcode = extern
+
+ def curl_easy_getinfo(curl: CURL,
+ option: CURLINFO,
+ parameter: CVararg*): CURLcode = extern
+
+ def curl_easy_cleanup(curl: CURL): Unit = extern
+
+ def curl_easy_strerror(curl: CURL, code: CURLcode): CString = extern
+
+ def curl_slist_append(head: curl_slist, string: CString): curl_slist = extern
+
+ def curl_slist_free_all(head: curl_slist): Unit = extern
+
+ def curl_version(): CString = extern
+
+}
diff --git a/client/src/http/package.scala b/client/src/http/package.scala
new file mode 100644
index 0000000..63b5405
--- /dev/null
+++ b/client/src/http/package.scala
@@ -0,0 +1,3 @@
+package triad
+
+package object http extends CurlBackend