Skip to content

Commit 5a24a06

Browse files
committed
Replace nanohttpd with Jetty
1 parent 3e9a66a commit 5a24a06

File tree

7 files changed

+93
-114
lines changed

7 files changed

+93
-114
lines changed

build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ buildscript {
1313

1414
subprojects {
1515
group = 'org.strykeforce.thirdcoast'
16-
version = '18.7.0'
16+
version = '18.7.1'
1717

1818
apply plugin: 'java-library'
1919
apply plugin: 'groovy'
@@ -51,4 +51,4 @@ subprojects {
5151
target '**/*.java'
5252
}
5353
}
54-
}
54+
}

telemetry/build.gradle

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ sourceCompatibility = 1.8
2323

2424

2525
repositories {
26-
maven { url = 'https://oss.sonatype.org/content/repositories/snapshots' } // nanohttpd
2726
maven { url = "https://dl.bintray.com/kotlin/kotlin-eap" } // FIXME
2827
}
2928

@@ -32,7 +31,7 @@ dependencies {
3231
compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
3332

3433
// telemetry
35-
implementation 'org.nanohttpd:nanohttpd:2.3.2-SNAPSHOT'
34+
implementation 'org.eclipse.jetty:jetty-server:9.4.12.v20180830'
3635
implementation 'com.squareup.moshi:moshi:1.5.0'
3736
implementation project(':swerve')
3837

Lines changed: 77 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -1,135 +1,112 @@
11
package org.strykeforce.thirdcoast.telemetry
22

3-
import com.squareup.moshi.JsonWriter
43
import mu.KotlinLogging
54
import okio.Buffer
6-
import org.nanohttpd.protocols.http.NanoHTTPD
7-
import org.nanohttpd.protocols.http.request.Method
8-
import org.nanohttpd.protocols.http.response.Response
9-
import org.nanohttpd.protocols.http.response.Status
5+
import org.eclipse.jetty.server.Request
6+
import org.eclipse.jetty.server.Server
7+
import org.eclipse.jetty.server.handler.AbstractHandler
8+
import org.eclipse.jetty.server.handler.DefaultHandler
9+
import org.eclipse.jetty.server.handler.HandlerList
1010
import org.strykeforce.thirdcoast.telemetry.grapher.ClientHandler
1111
import org.strykeforce.thirdcoast.telemetry.grapher.Subscription
12-
import java.io.IOException
1312
import java.net.Inet4Address
1413
import java.net.NetworkInterface
15-
import java.util.*
14+
import javax.servlet.http.HttpServletRequest
15+
import javax.servlet.http.HttpServletResponse
16+
1617

1718
private const val JSON = "application/json"
18-
private val logger = KotlinLogging.logger {}
19+
private const val GRAPHER = "/v1/grapher"
20+
private const val INVENTORY = "$GRAPHER/inventory"
21+
private const val SUBSCRIPTION = "$GRAPHER/subscription"
1922

20-
/** Provides a web service to config telemetry. */
21-
class TelemetryController(
22-
inventory: Inventory,
23-
private val clientHandler: ClientHandler,
24-
private val port: Int
25-
) : NanoHTTPD(port) {
23+
private val logger = KotlinLogging.logger {}
2624

27-
private val inventoryEndpoints: List<String>
28-
get() {
29-
val endpoints = ArrayList<String>(2)
30-
val nets = NetworkInterface.getNetworkInterfaces()
31-
for (netint in Collections.list(nets)) {
32-
val inetAddresses = netint.inetAddresses
33-
for (addr in Collections.list(inetAddresses)) {
34-
if (!addr.isLinkLocalAddress && addr.javaClass == Inet4Address::class.java)
35-
endpoints += "http://${addr.hostAddress}:$port/v1/grapher/inventory"
36-
}
37-
}
38-
return endpoints
25+
internal class TelemetryControllerHandler(private val inventory: Inventory, private val clientHandler: ClientHandler) :
26+
AbstractHandler() {
27+
28+
override fun handle(
29+
target: String,
30+
baseRequest: Request,
31+
request: HttpServletRequest,
32+
response: HttpServletResponse
33+
) {
34+
logger.debug { "${request.method} $target" }
35+
36+
baseRequest.isHandled = true
37+
38+
if (target.toLowerCase() == INVENTORY && request.method == "GET") {
39+
val buffer = Buffer()
40+
inventory.writeInventory(buffer)
41+
response.writeJson(buffer)
42+
logger.info { "inventory requested from ${request.remoteAddr}" }
43+
return
3944
}
4045

41-
init {
42-
addHTTPInterceptor { session ->
43-
if (session.method == Method.GET && session.uri.equals(
44-
"/v1/grapher/inventory",
45-
ignoreCase = true
46-
)
47-
) {
46+
if (target.toLowerCase() == SUBSCRIPTION) {
47+
if (request.method == "POST") {
48+
val sub = Subscription(inventory, request.remoteAddr, request.reader.readText())
49+
clientHandler.start(sub)
4850
val buffer = Buffer()
49-
inventory.writeInventory(buffer)
51+
sub.toJson(buffer)
52+
response.writeJson(buffer)
53+
logger.info { "subscription started from ${request.remoteAddr}" }
54+
return
55+
}
5056

51-
logger.debug { "inventory requested from ${session.remoteIpAddress}" }
52-
return@addHTTPInterceptor Response.newFixedLengthResponse(Status.OK, JSON, buffer.readByteArray())
57+
if (request.method == "DELETE") {
58+
clientHandler.shutdown()
59+
logger.info { "subscription stopped from ${request.remoteAddr}" }
60+
return
5361
}
54-
null
5562
}
63+
baseRequest.isHandled = false
64+
}
65+
}
5666

57-
addHTTPInterceptor { session ->
58-
if (session.method == Method.POST && session.uri.equals(
59-
"/v1/grapher/subscription",
60-
ignoreCase = true
61-
)
62-
) {
63-
val body = HashMap<String, String>()
64-
try {
65-
session.parseBody(body)
66-
val sub = Subscription(inventory, session.remoteIpAddress, body["postData"]!!)
67-
clientHandler.start(sub)
68-
val buffer = Buffer()
69-
sub.toJson(buffer)
70-
return@addHTTPInterceptor Response.newFixedLengthResponse(Status.OK, JSON, buffer.readByteArray())
71-
} catch (t: Throwable) {
72-
logger.error("couldn't start grapher", t)
73-
return@addHTTPInterceptor errorResponseFor(t)
74-
}
67+
private fun HttpServletResponse.writeJson(buffer: Buffer) {
68+
this.contentType = JSON
69+
this.status = HttpServletResponse.SC_OK
70+
this.writer.print(buffer.readUtf8())
71+
}
7572

76-
}
77-
null
78-
}
73+
/** Provides a web service to config telemetry. */
74+
class TelemetryController(
75+
inventory: Inventory,
76+
private val clientHandler: ClientHandler,
77+
private val port: Int
78+
) {
7979

80-
addHTTPInterceptor { session ->
81-
if (session.method == Method.DELETE && session.uri.equals(
82-
"/v1/grapher/subscription",
83-
ignoreCase = true
84-
)
85-
) {
86-
try {
87-
clientHandler.shutdown()
88-
return@addHTTPInterceptor Response.newFixedLengthResponse(Status.NO_CONTENT, JSON, "")
89-
} catch (t: Throwable) {
90-
logger.error("couldn't stop grapher", t)
91-
return@addHTTPInterceptor errorResponseFor(t)
92-
}
9380

94-
}
95-
null
81+
private val server = Server(port).apply {
82+
handler = HandlerList().apply {
83+
handlers = arrayOf(TelemetryControllerHandler(inventory, clientHandler), DefaultHandler())
9684
}
9785
}
9886

99-
/** Start web service to listen for HTTP commands that control telemetry service. */
100-
override fun start() {
101-
try {
102-
start(NanoHTTPD.SOCKET_READ_TIMEOUT, true)
103-
} catch (e: IOException) {
104-
logger.error("couldn't start web service", e)
105-
}
106-
107-
if (logger.isInfoEnabled) {
108-
logger.info("started web service")
109-
for (end in inventoryEndpoints) {
110-
logger.info(end)
87+
private val inventoryEndpoints: List<String>
88+
get() {
89+
val endpoints = mutableListOf<String>()
90+
NetworkInterface.getNetworkInterfaces().iterator().forEach { ni ->
91+
ni.inetAddresses.iterator().forEach { addr ->
92+
if (addr is Inet4Address && !addr.isLinkLocalAddress)
93+
endpoints += "http://${addr.hostAddress}:$port$INVENTORY"
94+
}
11195
}
96+
return endpoints
11297
}
98+
99+
/** Start web service to listen for HTTP commands that control telemetry service. */
100+
fun start() {
101+
server.start()
102+
logger.info("started web service")
103+
inventoryEndpoints.forEach(logger::info)
113104
}
114105

115106
/** Stop streaming to client and shut down web service. */
116107
fun shutdown() {
117108
clientHandler.shutdown()
118-
super.stop()
109+
server.stop()
119110
logger.info("stopped web service")
120111
}
121-
122-
private fun errorResponseFor(e: Throwable): Response {
123-
val buffer = Buffer()
124-
val writer = JsonWriter.of(buffer)
125-
try {
126-
writer.beginObject()
127-
writer.name("error").value(e.message)
128-
writer.endObject()
129-
} catch (ignored: IOException) {
130-
}
131-
132-
return Response.newFixedLengthResponse(Status.INTERNAL_ERROR, JSON, buffer.readByteArray())
133-
}
134-
135112
}

telemetry/src/main/kotlin/org/strykeforce/thirdcoast/telemetry/TelemetryService.kt

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,29 +23,26 @@ class TelemetryService(private val telemetryControllerFactory: Function<Inventor
2323

2424
private val items = LinkedHashSet<Item>()
2525
private var telemetryController: TelemetryController? = null
26-
private var running = false
2726

2827
/** Start the Telemetry service and listen for client connections. */
2928
fun start() {
30-
if (running) {
29+
if (telemetryController != null) {
3130
logger.info("already started")
3231
return
3332
}
3433
telemetryController = telemetryControllerFactory.apply(RobotInventory(items)).also { it.start() }
3534
logger.info("started telemetry controller")
36-
running = true
3735
}
3836

3937
/** Stop the Telemetry service. */
4038
fun stop() {
41-
if (!running) {
39+
if (telemetryController == null) {
4240
logger.info("already stopped")
4341
return
4442
}
4543
telemetryController?.shutdown()
4644
telemetryController = null
4745
logger.info("stopped")
48-
running = false
4946
}
5047

5148
/**
@@ -130,7 +127,7 @@ class TelemetryService(private val telemetryControllerFactory: Function<Inventor
130127
}
131128

132129
private fun checkNotStarted() {
133-
if (running) {
130+
if (telemetryController != null) {
134131
throw IllegalStateException("TelemetryService must be stopped.")
135132
}
136133
}

telemetry/src/main/kotlin/org/strykeforce/thirdcoast/telemetry/grapher/Measure.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package org.strykeforce.thirdcoast.telemetry.grapher
22

33
/** Available telemetry measurement types. */
44
enum class Measure constructor(val description: String) {
5+
UNKNOWN("Unknown Measurement"),
56
CLOSED_LOOP_TARGET("Closed-loop Setpoint"),
67

78
// BaseMotorController

telemetry/src/main/kotlin/org/strykeforce/thirdcoast/telemetry/grapher/Subscription.kt

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@ package org.strykeforce.thirdcoast.telemetry.grapher
22

33
import com.squareup.moshi.JsonWriter
44
import com.squareup.moshi.Moshi
5+
import mu.KotlinLogging
56
import okio.BufferedSink
67
import org.strykeforce.thirdcoast.telemetry.Inventory
78
import java.io.IOException
89
import java.util.*
910
import java.util.function.DoubleSupplier
1011

11-
//private val logger = KotlinLogging.logger {}
12+
private val logger = KotlinLogging.logger {}
1213

1314
/** Represents a subscription request for streaming data. */
1415
class Subscription(inventory: Inventory, val client: String, requestJson: String) {
@@ -19,7 +20,12 @@ class Subscription(inventory: Inventory, val client: String, requestJson: String
1920
val request: RequestJson = RequestJson.fromJson(requestJson) ?: RequestJson.EMPTY
2021
request.subscription.forEach {
2122
val item = inventory.itemForId(it.itemId)
22-
val measure = Measure.valueOf(it.measurementId)
23+
val measure = try {
24+
Measure.valueOf(it.measurementId)
25+
} catch (e: IllegalArgumentException) {
26+
logger.error { "no such measure \"${it.measurementId}\", request JSON = \n$requestJson" }
27+
Measure.UNKNOWN
28+
}
2329
measurements += item.measurementFor(measure)
2430
descriptions += "${item.description}: ${measure.description}"
2531
}

telemetry/src/main/kotlin/org/strykeforce/thirdcoast/telemetry/item/TalonItem.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,8 @@ class TalonItem @JvmOverloads constructor(
4646
private val sensorCollection = talon.sensorCollection
4747

4848
override fun measurementFor(measure: Measure): DoubleSupplier {
49-
require(measures.contains(measure))
50-
5149
return when (measure) {
50+
UNKNOWN -> DoubleSupplier { 2767.0 }
5251
CLOSED_LOOP_TARGET -> DoubleSupplier { talon.getClosedLoopTarget(0).toDouble() }
5352
OUTPUT_CURRENT -> DoubleSupplier { talon.outputCurrent }
5453
OUTPUT_VOLTAGE -> DoubleSupplier { talon.motorOutputVoltage }

0 commit comments

Comments
 (0)