Git Product home page Git Product logo

http4s-netty's Introduction

Http4s Build Status Maven Central Typelevel library Cats friendly

Http4s is a minimal, idiomatic Scala interface for HTTP services. Http4s is Scala's answer to Ruby's Rack, Python's WSGI, Haskell's WAI, and Java's Servlets.

val http = HttpRoutes.of {
  case GET -> Root / "hello" =>
    Ok("Hello, better world.")
}

Learn more at http4s.org.

If you run into any difficulties please enable partial unification in your build.sbt (not needed for Scala 2.13 and beyond, because Scala 2.13.0+ has partial unification switched on by default)

scalacOptions ++= Seq("-Ypartial-unification")

Requirements

Running the blaze backend requires a modern, supported version of the JVM to build and run, as it relies on server APIs unavailable before JDK8u252. Any JDK newer than JDK8u252, including 9+ is supported.

Code of Conduct

http4s is proud to be a Typelevel project. We are committed to providing a friendly, safe and welcoming environment for all, and ask that the community adhere to the Scala Code of Conduct.

License

This software is licensed under the Apache 2 license, quoted below.

Copyright 2013-2021 http4s [https://http4s.org]

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

[http://www.apache.org/licenses/LICENSE-2.0]

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

Acknowledgments

YourKit

Special thanks to YourKit for supporting this project's ongoing performance tuning efforts with licenses to their excellent product.

http4s-netty's People

Contributors

bryce-anderson avatar devilab avatar fsamier avatar hamnis avatar http4s-steward[bot] avatar rafalsumislawski avatar scala-steward avatar wjoel avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

http4s-netty's Issues

NettyClientBuilder.idleTimeout has no effect

Hello, I'm currently trying to investigate an issue with idleTimeout taking no effect and not closing long-running idle connection.

Expected behaviour:
If server not responding within idleTimeout (we can consider this as a connection idleness, right?) -- client.run effect should fail.

Actual behaviour:
client.run effect hangs until server responds or closing connection.

There is a test reproducing this (I wanted to push it as a branch, but looks like have no rights):

// client/src/test/scala/org/http4s/netty/client/NettyClientIdleTimeoutTest.scala
package org.http4s.netty.client

import cats.effect.IO
import com.comcast.ip4s._
import munit.catseffect.IOFixture
import org.http4s.HttpRoutes
import org.http4s.Request
import org.http4s.Response
import org.http4s.client.Client
import org.http4s.dsl.io._
import org.http4s.ember.client.EmberClientBuilder
import org.http4s.ember.server.EmberServerBuilder
import org.http4s.implicits._
import org.http4s.server.Server

import scala.concurrent.duration._

class NettyClientIdleTimeoutTest extends IOSuite {
  override val munitIOTimeout = 1.minute

  val emberClient: IOFixture[Client[IO]] =
    resourceFixture(
      EmberClientBuilder
        .default[IO]
        .withIdleConnectionTime(5.seconds)
        .build,
      "ember client")

  val nettyClient: IOFixture[Client[IO]] =
    resourceFixture(
      NettyClientBuilder[IO]
        .withIdleTimeout(5.seconds)
        .resource,
      "netty client")

  val server: IOFixture[Server] = resourceFixture(
    EmberServerBuilder
      .default[IO]
      .withPort(port"0")
      .withHttpApp(
        HttpRoutes
          .of[IO] { case GET -> Root / "idle-timeout" =>
            IO.sleep(30.seconds).as(Response())
          }
          .orNotFound
      )
      .build,
    "server"
  )

  List(
    (emberClient, "ember client"),
    (nettyClient, "netty client")
  ).foreach { case (client, name) =>
    test(s"$name fails after idle timeout") {
      val s = server()

      val req = Request[IO](uri = s.baseUri / "idle-timeout")
      val response = client().run(req).allocated.attempt
      IO.race(response, IO.sleep(15.seconds)).map {
        case Left(Left(error)) => println(s"response failed, error:"); error.printStackTrace()
        case Left(Right(_)) => println("response available")
        case Right(_) => fail("idle timeout wasn't triggered")
      }
    }
  }
}

Having TRACE level enabled in logback-test.xml, I do see several

13:15:18.180 [KQueueEventLoopGroup-2-2] TRACE o.h.n.c.Http4sHandler - Closing connection due to idle timeout 
13:15:23.179 [KQueueEventLoopGroup-2-2] TRACE o.h.n.c.Http4sHandler - Closing connection due to idle timeout

lines logged, but nothing happens from client perspective (client().run(req) don't finishes).

Please note, this test is passing for Ember client with it's withIdleConnectionTime.

Please correct me in case my expectations about NettyClientBuilder.idleTimeout are wrong.

netty client does not always seem ready to connect in test

io.netty.channel.StacklessClosedChannelException
  | => sat io.netty.channel.AbstractChannel$AbstractUnsafe.write(Object, ChannelPromise)(Unknown Source)
        at async_ @ org.http4s.netty.package$NettyFutureSyntax$.$anonfun$liftToF$1(package.scala:41)
        at delay @ org.http4s.netty.client.NettyClientBuilder.$anonfun$mkClient$4(NettyClientBuilder.scala:145)
        at flatMap @ org.http4s.netty.package$NettyFutureSyntax$.liftToF$extension(package.scala:40)
        at unsafeRunAndForget @ org.http4s.netty.client.NettyClientBuilder.$anonfun$mkClient$4(NettyClientBuilder.scala:145)

Leaves connections active when dropped

When I was load testing I noticed the server will maintain dropped connections forever. Noticed this when using the default metrics middleware & prometheus

Http3 support

There is an incubation module we can start with here
Examples are found in the tests

  • server
  • client

Investigate issue on Main

java.io.IOException: HTTP/1.1 header parser received no bytes
  | => sat java.net.http/jdk.internal.net.http.common.Utils.wrapWithExtraDetail(Utils.java:348)
        at java.net.http/jdk.internal.net.http.Http1Response$HeadersReader.onReadError(Http1Response.java:675)
	at java.net.http/jdk.internal.net.http.Http1AsyncReceiver.checkForErrors(Http1AsyncReceiver.java:302)
	at java.net.http/jdk.internal.net.http.Http1AsyncReceiver.flush(Http1AsyncReceiver.java:268)
	at java.net.http/jdk.internal.net.http.common.SequentialScheduler$LockingRestartableTask.run(SequentialScheduler.java:205)
	at java.net.http/jdk.internal.net.http.common.SequentialScheduler$CompleteRestartableTask.run(SequentialScheduler.java:149)
	at java.net.http/jdk.internal.net.http.common.SequentialScheduler$SchedulableTask.run(SequentialScheduler.java:230)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
	at java.base/java.lang.Thread.run(Thread.java:833)
	at async_ @ org.http4s.jdkhttpclient.package$.fromCompletableFuture$$anonfun$1(package.scala:47)
	at delay @ org.http4s.jdkhttpclient.JdkHttpClient$.apply$$anonfun$1$$anonfun$1$$anonfun$1(JdkHttpClient.scala:246)
	at use @ org.http4s.client.DefaultClient.expectOr(DefaultClient.scala:103)
	at product @ fs2.concurrent.SignallingRef$.of(Signal.scala:203)
	at tupled @ org.http4s.jdkhttpclient.JdkHttpClient$.convertResponse$1(JdkHttpClient.scala:177)
Caused by: java.io.EOFException: EOF reached while reading
	at java.net.http/jdk.internal.net.http.Http1AsyncReceiver$Http1TubeSubscriber.onComplete(Http1AsyncReceiver.java:596)
	at java.net.http/jdk.internal.net.http.SocketTube$InternalReadPublisher$ReadSubscription.signalCompletion(SocketTube.java:640)
	at java.net.http/jdk.internal.net.http.SocketTube$InternalReadPublisher$InternalReadSubscription.read(SocketTube.java:845)
	at java.net.http/jdk.internal.net.http.SocketTube$SocketFlowTask.run(SocketTube.java:181)
	at java.net.http/jdk.internal.net.http.common.SequentialScheduler$SchedulableTask.run(SequentialScheduler.java:230)
	at java.net.http/jdk.internal.net.http.common.SequentialScheduler.runOrSchedule(SequentialScheduler.java:303)
	at java.net.http/jdk.internal.net.http.common.SequentialScheduler.runOrSchedule(SequentialScheduler.java:256)
	at java.net.http/jdk.internal.net.http.SocketTube$InternalReadPublisher$InternalReadSubscription.signalReadable(SocketTube.java:774)
	at java.net.http/jdk.internal.net.http.SocketTube$InternalReadPublisher$ReadEvent.signalEvent(SocketTube.java:957)
	at java.net.http/jdk.internal.net.http.SocketTube$SocketFlowEvent.handle(SocketTube.java:253)
	at java.net.http/jdk.internal.net.http.HttpClientImpl$SelectorManager.handleEvent(HttpClientImpl.java:979)
	at java.net.http/jdk.internal.net.http.HttpClientImpl$SelectorManager.lambda$run$3(HttpClientImpl.java:934)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
	at java.net.http/jdk.internal.net.http.HttpClientImpl$SelectorManager.run(HttpClientImpl.java:934)

Flow type?

I noticed the reactive-streams upgrade, and if I'm reading right, this project requires Java 11. This might be of interest on the new fs2:

typelevel/fs2#3102

Idle timeout causes response A sent to request B

Hello, using recently fixed idleTimeout leads us to the following situation:

  1. Sending request A, channel a created
  2. Request A times out
  3. Sending request B, channel a reused
  4. Server is ready with response A
  5. Client sent request B receives response A via channel a ๐Ÿ˜ฑ

Expected behavior
Request B receives response B

Please find some tests reproducing the issue below (still have no right to push it as branch):

/Users/nik.v.kharitonov/Projects/http4s-netty/client/src/test/scala/org/http4s/netty/client/NettyClientResponseConsistencyTest.scala

package org.http4s.netty.client

import cats.effect.IO
import cats.effect.std.Random
import com.comcast.ip4s._
import munit.catseffect.IOFixture
import org.http4s.HttpRoutes
import org.http4s.Request
import org.http4s.client.Client
import org.http4s.dsl.io._
import org.http4s.ember.server.EmberServerBuilder
import org.http4s.implicits._
import org.http4s.server.Server

import scala.concurrent.duration._

/** Having request A -> response A and request B -> response B, we should never receive response B
  * for request A.
  */
class NettyClientResponseConsistencyTest extends IOSuite {
  override val munitIOTimeout: Duration = 10.minute

  def print(msg: String): IO[Unit] =
    IO.realTimeInstant.flatMap(now => IO.println(s"$now ${Thread.currentThread().getName()} $msg"))

  val client: IOFixture[Client[IO]] =
    resourceFixture(NettyClientBuilder[IO].resource, "netty client")

  val clientWithTimeout: IOFixture[Client[IO]] =
    resourceFixture(
      NettyClientBuilder[IO].withIdleTimeout(4.seconds).resource,
      "netty client with timeout")

  val server: IOFixture[Server] = resourceFixture(
    EmberServerBuilder
      .default[IO]
      .withPort(port"0")
      .withHttpApp(
        HttpRoutes
          .of[IO] {
            case GET -> Root / "1" =>
              print("server: received /1 request, sleeping...") *>
                IO.sleep(5.seconds) *>
                print("server: responding with 1") *> Ok("1")
            case GET -> Root / "2" =>
              print("server: received /2 request, sleeping...") *>
                IO.sleep(2.seconds) *>
                print("server: responding with 2") *> Ok("2")
          }
          .orNotFound
      )
      .build,
    "server"
  )

  val serverRandom: IOFixture[Server] = resourceFixture(
    {
      val serverBoundMillis = 5000
      val r = Random.javaUtilConcurrentThreadLocalRandom[IO]
      EmberServerBuilder
        .default[IO]
        .withPort(port"0")
        .withHttpApp(
          HttpRoutes
            .of[IO] { case GET -> Root / i =>
              print(s"server: received /$i request, sleeping...") *>
                r.nextIntBounded(serverBoundMillis).map(_.millis).flatMap(IO.sleep) *>
                print(s"server: responding with $i") *> Ok(i.toString())
            }
            .orNotFound
        )
        .build
    },
    "server random"
  )

  test("two simple requests") {
    val s = server()
    val c = client()

    val req1 = Request[IO](uri = s.baseUri / "1")
    val req2 = Request[IO](uri = s.baseUri / "2")
    for {
      f1 <- c.expect[String](req1).start
      f2 <- c.expect[String](req2).start
      r1 <- f1.joinWithNever
      r2 <- f2.joinWithNever
    } yield {
      assertEquals(r1, "1")
      assertEquals(r2, "2")
    }
  }

  // not passing, use `client()` instead of `clientWithTimeout` to pass
  test("Request A timed out, request B receives response B") {
    val s = server()
    val c = clientWithTimeout()

    val req1 = Request[IO](uri = s.baseUri / "1")
    val req2 = Request[IO](uri = s.baseUri / "2")
    for {
      _ <- c.expect[String](req1).attempt
      r2 <- c.expect[String](req2)
    } yield assertEquals(r2, "2")
  }

  // not passing, use `client()` instead of `clientWithTimeout` to pass
  test("A mess of requests should still be consistent with responses") {
    val uniqueRoutes = 5
    val parallelism = 10
    val repeat = 40L

    val s = serverRandom()
    val c = clientWithTimeout()

    def checker(i: Int): IO[Unit] = print(s"client: sending /$i request") *>
      c.expect[String](Request[IO](uri = s.baseUri / i.toString()))
        .attempt
        .flatMap {
          case Right(r) => IO(assertEquals(r, i.toString()))
          case Left(error) => print(s"catched error for /$i: $error")
        }

    fs2.Stream
      .unfold(0) { case i => Option.when(i < uniqueRoutes)((checker(i), i + 1)) }
      .repeatN(repeat)
      .parEvalMapUnordered[IO, Unit](parallelism)(identity)
      .compile
      .drain
  }
}

Environment

  • http4s-netty on v0.5.14 tag
  • MacOS 13.5 with Apple M2 Pro

On JDK 21, hangs on startup

11:32:19.481 [io-compute-blocker-5] DEBUG i.n.u.i.l.InternalLoggerFactory - Using SLF4J as the default logging framework
11:32:19.489 [io-compute-blocker-5] DEBUG i.n.u.i.PlatformDependent0 - -Dio.netty.noUnsafe: false
11:32:19.489 [io-compute-blocker-5] DEBUG i.n.u.i.PlatformDependent0 - Java version: 21
11:32:19.489 [io-compute-blocker-5] DEBUG i.n.u.i.PlatformDependent0 - sun.misc.Unsafe.theUnsafe: available
11:32:19.490 [io-compute-blocker-5] DEBUG i.n.u.i.PlatformDependent0 - sun.misc.Unsafe.copyMemory: available
11:32:19.490 [io-compute-blocker-5] DEBUG i.n.u.i.PlatformDependent0 - sun.misc.Unsafe.storeFence: available
11:32:19.490 [io-compute-blocker-5] DEBUG i.n.u.i.PlatformDependent0 - java.nio.Buffer.address: available
11:32:19.490 [io-compute-blocker-5] DEBUG i.n.u.i.PlatformDependent0 - direct buffer constructor: unavailable
java.lang.UnsupportedOperationException: Reflective setAccessible(true) disabled
	at io.netty.util.internal.ReflectionUtil.trySetAccessible(ReflectionUtil.java:31)
	at io.netty.util.internal.PlatformDependent0$5.run(PlatformDependent0.java:293)
	at java.base/java.security.AccessController.doPrivileged(AccessController.java:319)
	at io.netty.util.internal.PlatformDependent0.<clinit>(PlatformDependent0.java:286)
	at io.netty.util.internal.PlatformDependent.isAndroid(PlatformDependent.java:331)
	at io.netty.util.internal.PlatformDependent.<clinit>(PlatformDependent.java:86)
	at io.netty.util.internal.ClassInitializerUtil.tryLoadClasses(ClassInitializerUtil.java:32)
	at io.netty.channel.kqueue.Native.<clinit>(Native.java:65)
	at io.netty.channel.kqueue.KQueue.<clinit>(KQueue.java:38)
	at org.http4s.netty.server.NettyServerBuilder.selectNative(NettyServerBuilder.scala:150)
	at org.http4s.netty.server.NettyServerBuilder.getEventLoop(NettyServerBuilder.scala:128)
	at org.http4s.netty.server.NettyServerBuilder.bind(NettyServerBuilder.scala:253)
	at org.http4s.netty.server.NettyServerBuilder.resource$$anonfun$1$$anonfun$2$$anonfun$1(NettyServerBuilder.scala:295)
	at cats.effect.IOFiber.runLoop(IOFiber.scala:343)
	at cats.effect.IOFiber.execR(IOFiber.scala:1366)
	at cats.effect.IOFiber.run(IOFiber.scala:112)
	at cats.effect.unsafe.WorkerThread.run(WorkerThread.scala:702)
11:32:19.491 [io-compute-blocker-5] DEBUG i.n.u.i.PlatformDependent0 - java.nio.Bits.unaligned: available, true
11:32:19.491 [io-compute-blocker-5] DEBUG i.n.u.i.PlatformDependent0 - jdk.internal.misc.Unsafe.allocateUninitializedArray(int): unavailable
java.lang.IllegalAccessException: class io.netty.util.internal.PlatformDependent0$7 cannot access class jdk.internal.misc.Unsafe (in module java.base) because module java.base does not export jdk.internal.misc to unnamed module @4d5804c6
	at java.base/jdk.internal.reflect.Reflection.newIllegalAccessException(Reflection.java:394)
	at java.base/java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:714)
	at java.base/java.lang.reflect.Method.invoke(Method.java:571)
	at io.netty.util.internal.PlatformDependent0$7.run(PlatformDependent0.java:429)
	at java.base/java.security.AccessController.doPrivileged(AccessController.java:319)
	at io.netty.util.internal.PlatformDependent0.<clinit>(PlatformDependent0.java:420)
	at io.netty.util.internal.PlatformDependent.isAndroid(PlatformDependent.java:331)
	at io.netty.util.internal.PlatformDependent.<clinit>(PlatformDependent.java:86)
	at io.netty.util.internal.ClassInitializerUtil.tryLoadClasses(ClassInitializerUtil.java:32)
	at io.netty.channel.kqueue.Native.<clinit>(Native.java:65)
	at io.netty.channel.kqueue.KQueue.<clinit>(KQueue.java:38)
	at org.http4s.netty.server.NettyServerBuilder.selectNative(NettyServerBuilder.scala:150)
	at org.http4s.netty.server.NettyServerBuilder.getEventLoop(NettyServerBuilder.scala:128)
	at org.http4s.netty.server.NettyServerBuilder.bind(NettyServerBuilder.scala:253)
	at org.http4s.netty.server.NettyServerBuilder.resource$$anonfun$1$$anonfun$2$$anonfun$1(NettyServerBuilder.scala:295)
	at cats.effect.IOFiber.runLoop(IOFiber.scala:343)
	at cats.effect.IOFiber.execR(IOFiber.scala:1366)
	at cats.effect.IOFiber.run(IOFiber.scala:112)
	at cats.effect.unsafe.WorkerThread.run(WorkerThread.scala:702)
11:32:19.494 [io-compute-blocker-5] DEBUG i.n.u.i.PlatformDependent0 - java.nio.DirectByteBuffer.<init>(long, {int,long}): unavailable
11:32:19.494 [io-compute-blocker-5] DEBUG i.n.u.i.PlatformDependent - sun.misc.Unsafe: available
11:32:19.494 [io-compute-blocker-5] DEBUG i.n.u.i.PlatformDependent - -Dio.netty.tmpdir: /var/folders/gd/4mv4d8q553g790vd9scjd0pw0000gn/T (java.io.tmpdir)
11:32:19.494 [io-compute-blocker-5] DEBUG i.n.u.i.PlatformDependent - -Dio.netty.bitMode: 64 (sun.arch.data.model)
11:32:19.495 [io-compute-blocker-5] DEBUG i.n.u.i.PlatformDependent - Platform: MacOS
11:32:19.495 [io-compute-blocker-5] DEBUG i.n.u.i.PlatformDependent - -Dio.netty.maxDirectMemory: -1 bytes
11:32:19.495 [io-compute-blocker-5] DEBUG i.n.u.i.PlatformDependent - -Dio.netty.uninitializedArrayAllocationThreshold: -1
11:32:19.495 [io-compute-blocker-5] DEBUG i.n.u.i.CleanerJava9 - java.nio.ByteBuffer.cleaner(): available
11:32:19.495 [io-compute-blocker-5] DEBUG i.n.u.i.PlatformDependent - -Dio.netty.noPreferDirect: false
11:32:19.497 [io-compute-blocker-5] DEBUG i.n.u.i.NativeLibraryLoader - -Dio.netty.native.workdir: /var/folders/gd/4mv4d8q553g790vd9scjd0pw0000gn/T (io.netty.tmpdir)
11:32:19.497 [io-compute-blocker-5] DEBUG i.n.u.i.NativeLibraryLoader - -Dio.netty.native.deleteLibAfterLoading: true
11:32:19.497 [io-compute-blocker-5] DEBUG i.n.u.i.NativeLibraryLoader - -Dio.netty.native.tryPatchShadedId: true
11:32:19.497 [io-compute-blocker-5] DEBUG i.n.u.i.NativeLibraryLoader - -Dio.netty.native.detectNativeLibraryDuplicates: true

it just hangs at that point and doesn't even respond to sigterm -- any thoughts?

http/2

Add codecs and pipeline for http/2

  • server
  • client

JDKWebsocketTest flake

Feb 04, 2023 11:09:13 AM io.netty.channel.AbstractChannel$AbstractUnsafe invokeLater
WARNING: Can't invoke task later as EventLoop rejected it
java.util.concurrent.RejectedExecutionException: event executor terminated

I assume this has something to do with the new server changes.

Seeing Netty pipeline exceptions when the Http4sHandler tries to remove itself

java.util.NoSuchElementException: org.http4s.netty.client.Http4sHandler
  | => sat io.netty.channel.DefaultChannelPipeline.getContextOrDie(DefaultChannelPipeline.java:1082)
	at io.netty.channel.DefaultChannelPipeline.remove(DefaultChannelPipeline.java:417)
	at org.http4s.netty.client.Http4sHandler.$anonfun$channelRead$3(Http4sHandler.scala:53)
	at tupleRight @ org.http4s.netty.NettyModelConversion.fromNettyResponse(NettyModelConversion.scala:98)
	at map @ org.http4s.netty.client.Http4sHandler.channelRead(Http4sHandler.scala:47)
	at map @ org.http4s.netty.client.Http4sHandler.channelRead(Http4sHandler.scala:51)
	at unsafeRunAndForget @ org.http4s.netty.client.NettyClientBuilder.$anonfun$mkClient$4(NettyClientBuilder.scala:145)

This is all but certainly due to a race condition: if the channel is closed the pipeline is torn down. The handler is likely attempting to remove itself after that has already happened and not finding itself in the pipeline.

DomainSocket support

Hi, are you interested in adding domain sockets support? they are very useful in handful of scenarios :) I already started working on this and it seems not too difficult ... let me know what you think in #134 :)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.