not using jetty
This commit is contained in:
parent
2b212b9607
commit
c3e8bf240d
11 changed files with 277 additions and 218 deletions
48
build.gradle
48
build.gradle
|
@ -1,47 +1,51 @@
|
|||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
|
||||
plugins {
|
||||
id 'org.springframework.boot' version '3.1.4'
|
||||
id 'io.spring.dependency-management' version '1.1.3'
|
||||
id 'org.jetbrains.kotlin.jvm' version '1.8.22'
|
||||
id 'org.jetbrains.kotlin.plugin.spring' version '1.8.22'
|
||||
id "org.jetbrains.kotlin.plugin.jpa" version '1.8.22'
|
||||
id 'org.springframework.boot' version '3.1.4'
|
||||
id 'io.spring.dependency-management' version '1.1.3'
|
||||
id 'org.jetbrains.kotlin.jvm' version '1.8.22'
|
||||
id 'org.jetbrains.kotlin.plugin.spring' version '1.8.22'
|
||||
id "org.jetbrains.kotlin.plugin.jpa" version '1.8.22'
|
||||
}
|
||||
|
||||
group = 'net.schowek'
|
||||
version = '0.0.1-SNAPSHOT'
|
||||
|
||||
java {
|
||||
sourceCompatibility = '17'
|
||||
sourceCompatibility = '17'
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'org.springframework.boot:spring-boot-starter-web'
|
||||
implementation 'com.fasterxml.jackson.module:jackson-module-kotlin'
|
||||
implementation 'org.jetbrains.kotlin:kotlin-reflect'
|
||||
implementation 'io.github.microutils:kotlin-logging:3.0.5'
|
||||
implementation('org.springframework.boot:spring-boot-starter-web') {
|
||||
exclude group: 'org.springframework.boot', module: 'spring-boot-starter-tomcat'
|
||||
}
|
||||
implementation 'org.springframework.boot:spring-boot-starter-undertow'
|
||||
|
||||
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
|
||||
implementation 'org.mariadb.jdbc:mariadb-java-client:3.2.0'
|
||||
implementation 'com.fasterxml.jackson.module:jackson-module-kotlin'
|
||||
implementation 'org.jetbrains.kotlin:kotlin-reflect'
|
||||
implementation 'io.github.microutils:kotlin-logging:3.0.5'
|
||||
|
||||
implementation 'org.jupnp:org.jupnp:2.7.1'
|
||||
implementation 'org.jupnp:org.jupnp.support:2.7.1'
|
||||
implementation 'org.apache.httpcomponents:httpclient:4.5.14'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
|
||||
implementation 'org.mariadb.jdbc:mariadb-java-client:3.2.0'
|
||||
|
||||
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
||||
implementation 'org.jupnp:org.jupnp:2.7.1'
|
||||
implementation 'org.jupnp:org.jupnp.support:2.7.1'
|
||||
implementation 'org.apache.httpcomponents:httpclient:4.5.14'
|
||||
|
||||
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
||||
}
|
||||
|
||||
tasks.withType(KotlinCompile) {
|
||||
kotlinOptions {
|
||||
freeCompilerArgs += '-Xjsr305=strict'
|
||||
jvmTarget = '17'
|
||||
}
|
||||
kotlinOptions {
|
||||
freeCompilerArgs += '-Xjsr305=strict'
|
||||
jvmTarget = '17'
|
||||
}
|
||||
}
|
||||
|
||||
tasks.named('test') {
|
||||
useJUnitPlatform()
|
||||
useJUnitPlatform()
|
||||
}
|
||||
|
|
|
@ -8,9 +8,9 @@ import org.springframework.stereotype.Component
|
|||
|
||||
@Component
|
||||
class NextcloudDLNA(
|
||||
private val dlnaService: DlnaService
|
||||
dlnaService: DlnaService
|
||||
) {
|
||||
private val upnpService: UpnpService = dlnaService.start()
|
||||
val upnpService: UpnpService = dlnaService.start()
|
||||
|
||||
@PreDestroy
|
||||
fun destroy() {
|
||||
|
|
|
@ -2,8 +2,10 @@ package net.schowek.nextclouddlna
|
|||
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication
|
||||
import org.springframework.boot.runApplication
|
||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc
|
||||
|
||||
@SpringBootApplication
|
||||
@EnableWebMvc
|
||||
class NextcloudDLNAApp
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
package net.schowek.nextclouddlna.controller
|
||||
|
||||
import UpnpStreamProcessor
|
||||
import jakarta.servlet.http.HttpServletRequest
|
||||
import mu.KLogging
|
||||
import net.schowek.nextclouddlna.NextcloudDLNA
|
||||
import net.schowek.nextclouddlna.dlna.media.MediaServer
|
||||
import org.springframework.core.io.InputStreamResource
|
||||
import org.springframework.core.io.Resource
|
||||
import org.springframework.http.HttpHeaders
|
||||
import org.springframework.http.HttpStatusCode
|
||||
import org.springframework.http.MediaType.APPLICATION_OCTET_STREAM_VALUE
|
||||
import org.springframework.http.ResponseEntity
|
||||
import org.springframework.stereotype.Controller
|
||||
import org.springframework.util.MultiValueMap
|
||||
import org.springframework.web.bind.annotation.PathVariable
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
import org.springframework.web.bind.annotation.RequestMethod.*
|
||||
import org.springframework.web.bind.annotation.ResponseBody
|
||||
|
||||
|
||||
@Controller
|
||||
class DLNAController(
|
||||
private val dlna: NextcloudDLNA,
|
||||
private val streamRequestMapper: StreamRequestMapper
|
||||
) {
|
||||
@RequestMapping(
|
||||
method = [GET, HEAD], value = ["/dev/{uid}/icon.png"],
|
||||
produces = [APPLICATION_OCTET_STREAM_VALUE]
|
||||
)
|
||||
@ResponseBody
|
||||
fun handleGetIcon(
|
||||
@PathVariable("uid") uid: String,
|
||||
request: HttpServletRequest
|
||||
): Resource {
|
||||
logger.info { "GET ICON request from ${request.remoteAddr}: ${request.requestURI}" }
|
||||
return InputStreamResource(MediaServer.iconResource());
|
||||
}
|
||||
|
||||
@RequestMapping(
|
||||
method = [GET, HEAD], value = [
|
||||
"/dev/{uid}/desc",
|
||||
"/dev/{uid}/svc/upnp-org/ContentDirectory/desc",
|
||||
"/dev/{uid}/svc/upnp-org/ConnectionManager/desc"
|
||||
],
|
||||
produces = ["application/xml;charset=utf8", "text/xml;charset=utf8"]
|
||||
)
|
||||
fun handleGet(
|
||||
@PathVariable("uid") uid: String,
|
||||
request: HttpServletRequest
|
||||
): ResponseEntity<Any> {
|
||||
logger.info { "GET request from ${request.remoteAddr}: ${request.requestURI}" }
|
||||
val r = UpnpStreamProcessor(dlna).processMessage(streamRequestMapper.map(request))
|
||||
return ResponseEntity(
|
||||
r.body,
|
||||
HttpHeaders().also { h -> r.headers.entries.forEach { h.add(it.key, it.value.joinToString { it }) } },
|
||||
HttpStatusCode.valueOf(r.operation.statusCode)
|
||||
)
|
||||
}
|
||||
|
||||
@RequestMapping(
|
||||
method = [POST], value = [
|
||||
"/dev/{uid}/svc/upnp-org/ContentDirectory/action"
|
||||
],
|
||||
produces = ["application/xml;charset=utf8", "text/xml;charset=utf8"]
|
||||
)
|
||||
fun handlePost(
|
||||
@PathVariable("uid") uid: String,
|
||||
request: HttpServletRequest
|
||||
): ResponseEntity<Any> {
|
||||
logger.info { "POST request from ${request.remoteAddr}: ${request.requestURI}" }
|
||||
val r = UpnpStreamProcessor(dlna).processMessage(streamRequestMapper.map(request))
|
||||
return ResponseEntity(
|
||||
r.body,
|
||||
HttpHeaders().also { h -> r.headers.entries.forEach { h.add(it.key, it.value.joinToString { it }) } },
|
||||
HttpStatusCode.valueOf(r.operation.statusCode)
|
||||
)
|
||||
}
|
||||
|
||||
companion object : KLogging()
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package net.schowek.nextclouddlna.controller
|
||||
|
||||
import mu.KLogging
|
||||
import org.springframework.http.HttpStatus.NOT_FOUND
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler
|
||||
import org.springframework.web.bind.annotation.ResponseBody
|
||||
import org.springframework.web.bind.annotation.ResponseStatus
|
||||
import org.springframework.web.context.request.WebRequest
|
||||
import org.springframework.web.servlet.NoHandlerFoundException
|
||||
|
||||
@ControllerAdvice
|
||||
class ErrorHandler {
|
||||
@ExceptionHandler(NoHandlerFoundException::class)
|
||||
@ResponseStatus(NOT_FOUND)
|
||||
@ResponseBody
|
||||
fun handleNoHandlerFound(e: NoHandlerFoundException, request: WebRequest): HashMap<String, String> {
|
||||
val response = HashMap<String, String>()
|
||||
response["status"] = "fail"
|
||||
response["message"] = e.localizedMessage
|
||||
logger.info { "404 from $request (${e.requestURL})" }
|
||||
return response
|
||||
}
|
||||
|
||||
companion object : KLogging()
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
package net.schowek.nextclouddlna.controller
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest
|
||||
import mu.KLogging
|
||||
import org.jupnp.model.message.*
|
||||
import org.springframework.stereotype.Component
|
||||
import java.net.InetAddress
|
||||
import java.net.URI
|
||||
|
||||
@Component
|
||||
class StreamRequestMapper {
|
||||
fun map(request: HttpServletRequest): StreamRequestMessage {
|
||||
val requestMessage = StreamRequestMessage(
|
||||
UpnpRequest.Method.getByHttpName(request.method),
|
||||
URI(request.requestURI)
|
||||
// TODO put request.inputStream.readBytes() here
|
||||
)
|
||||
if (requestMessage.operation.method == UpnpRequest.Method.UNKNOWN) {
|
||||
logger.warn("Method not supported by UPnP stack: {}", request.method)
|
||||
throw RuntimeException("Method not supported: {}" + request.method)
|
||||
}
|
||||
|
||||
requestMessage.connection = MyHttpServerConnection(request)
|
||||
requestMessage.headers = createHeaders(request)
|
||||
setBody(request, requestMessage)
|
||||
return requestMessage
|
||||
}
|
||||
|
||||
private fun setBody(
|
||||
request: HttpServletRequest,
|
||||
requestMessage: StreamRequestMessage
|
||||
) {
|
||||
val bodyBytes = request.inputStream.readBytes()
|
||||
|
||||
logger.debug(" Reading request body bytes: " + bodyBytes.size)
|
||||
if (bodyBytes.isNotEmpty() && requestMessage.isContentTypeMissingOrText) {
|
||||
logger.debug("Request contains textual entity body, converting then setting string on message")
|
||||
requestMessage.setBodyCharacters(bodyBytes)
|
||||
} else if (bodyBytes.isNotEmpty()) {
|
||||
logger.debug("Request contains binary entity body, setting bytes on message")
|
||||
requestMessage.setBody(UpnpMessage.BodyType.BYTES, bodyBytes)
|
||||
} else {
|
||||
logger.debug("Request did not contain entity body")
|
||||
}
|
||||
}
|
||||
|
||||
private fun createHeaders(request: HttpServletRequest): UpnpHeaders {
|
||||
val headers = mutableMapOf<String, List<String>>()
|
||||
with(request.headerNames) {
|
||||
if (this != null) {
|
||||
while (hasMoreElements()) {
|
||||
with(nextElement()) {
|
||||
headers[this] = listOf(request.getHeader(this))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return UpnpHeaders(headers)
|
||||
}
|
||||
|
||||
inner class MyHttpServerConnection(
|
||||
private val request: HttpServletRequest
|
||||
) : Connection {
|
||||
override fun isOpen(): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun getRemoteAddress(): InetAddress? {
|
||||
return if (request.remoteAddr != null) InetAddress.getByName(request.remoteAddr) else null
|
||||
}
|
||||
|
||||
override fun getLocalAddress(): InetAddress? {
|
||||
return if (request.localAddr != null) InetAddress.getByName(request.localAddr) else null
|
||||
}
|
||||
}
|
||||
|
||||
companion object : KLogging()
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
import mu.KLogging
|
||||
import net.schowek.nextclouddlna.NextcloudDLNA
|
||||
import org.jupnp.model.message.StreamRequestMessage
|
||||
import org.jupnp.model.message.StreamResponseMessage
|
||||
import org.jupnp.model.message.UpnpResponse
|
||||
import org.jupnp.transport.spi.UpnpStream
|
||||
|
||||
|
||||
class UpnpStreamProcessor(
|
||||
dlna: NextcloudDLNA
|
||||
) : UpnpStream(dlna.upnpService.protocolFactory) {
|
||||
|
||||
fun processMessage(requestMsg: StreamRequestMessage): StreamResponseMessage {
|
||||
logger.debug { "Processing $requestMsg" }
|
||||
var response = super.process(requestMsg)
|
||||
if (response == null) {
|
||||
response = StreamResponseMessage(UpnpResponse.Status.NOT_FOUND)
|
||||
}
|
||||
return response
|
||||
}
|
||||
|
||||
override fun run() {
|
||||
}
|
||||
|
||||
companion object : KLogging()
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
package net.schowek.nextclouddlna.dlna
|
||||
|
||||
import mu.KLogging
|
||||
import net.schowek.nextclouddlna.dlna.media.MediaServer
|
||||
import net.schowek.nextclouddlna.dlna.transport.ApacheStreamClient
|
||||
import net.schowek.nextclouddlna.dlna.transport.ApacheStreamClientConfiguration
|
||||
|
@ -7,9 +8,13 @@ import net.schowek.nextclouddlna.dlna.transport.MyStreamServerConfiguration
|
|||
import net.schowek.nextclouddlna.dlna.transport.MyStreamServerImpl
|
||||
import net.schowek.nextclouddlna.util.ServerInfoProvider
|
||||
import org.jupnp.DefaultUpnpServiceConfiguration
|
||||
import org.jupnp.UpnpService
|
||||
import org.jupnp.UpnpServiceConfiguration
|
||||
import org.jupnp.UpnpServiceImpl
|
||||
import org.jupnp.model.meta.LocalDevice
|
||||
import org.jupnp.protocol.ProtocolFactory
|
||||
import org.jupnp.protocol.ProtocolFactoryImpl
|
||||
import org.jupnp.protocol.async.SendingNotificationAlive
|
||||
import org.jupnp.registry.Registry
|
||||
import org.jupnp.transport.impl.NetworkAddressFactoryImpl
|
||||
import org.jupnp.transport.spi.NetworkAddressFactory
|
||||
|
@ -39,9 +44,13 @@ class DlnaService(
|
|||
override fun createRegistry(pf: ProtocolFactory): Registry {
|
||||
return RegistryImplWithOverrides(this)
|
||||
}
|
||||
|
||||
override fun createProtocolFactory(): ProtocolFactory? {
|
||||
return MyProtocolFactory(this)
|
||||
}
|
||||
}
|
||||
|
||||
private inner class MyUpnpServiceConfiguration : DefaultUpnpServiceConfiguration(8081) {
|
||||
private inner class MyUpnpServiceConfiguration : DefaultUpnpServiceConfiguration(8080) {
|
||||
override fun createStreamClient(): StreamClient<*> {
|
||||
return ApacheStreamClient(
|
||||
ApacheStreamClientConfiguration(syncProtocolExecutorService)
|
||||
|
@ -72,3 +81,14 @@ class DlnaService(
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
class MyProtocolFactory(
|
||||
upnpService: UpnpService
|
||||
) : ProtocolFactoryImpl(upnpService) {
|
||||
override fun createSendingNotificationAlive(localDevice: LocalDevice): SendingNotificationAlive {
|
||||
logger.info { "SENDING ALIVE $localDevice" }
|
||||
return SendingNotificationAlive(upnpService, localDevice)
|
||||
}
|
||||
|
||||
companion object : KLogging()
|
||||
}
|
|
@ -10,6 +10,7 @@ import org.springframework.beans.factory.annotation.Qualifier
|
|||
import org.springframework.beans.factory.annotation.Value
|
||||
import org.springframework.stereotype.Component
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
|
||||
|
||||
@Component
|
||||
|
@ -20,17 +21,17 @@ class MediaServer(
|
|||
private val connectionManagerService: LocalService<*>,
|
||||
@Value("\${server.friendlyName}")
|
||||
private val friendlyName: String,
|
||||
private val externalUrls: ExternalUrls
|
||||
externalUrls: ExternalUrls
|
||||
) {
|
||||
val device = LocalDevice(
|
||||
DeviceIdentity(uniqueSystemIdentifier("DLNAtoad-MediaServer"), 300),
|
||||
final val device = LocalDevice(
|
||||
DeviceIdentity(uniqueSystemIdentifier("Nextcloud-DLNA-MediaServer"), 300),
|
||||
UDADeviceType(DEVICE_TYPE, VERSION),
|
||||
DeviceDetails(friendlyName, externalUrls.selfURI),
|
||||
createDeviceIcon(), arrayOf(contentDirectoryService, connectionManagerService)
|
||||
createDeviceIcon(),
|
||||
arrayOf(contentDirectoryService, connectionManagerService)
|
||||
)
|
||||
|
||||
@PostConstruct
|
||||
fun init() {
|
||||
init {
|
||||
logger.info("uniqueSystemIdentifier: {} ({})", device.identity.udn, friendlyName)
|
||||
}
|
||||
|
||||
|
@ -41,14 +42,18 @@ class MediaServer(
|
|||
|
||||
@Throws(IOException::class)
|
||||
fun createDeviceIcon(): Icon {
|
||||
val resource = MediaServer::class.java.getResourceAsStream("/$ICON_FILENAME")
|
||||
?: throw IllegalStateException("Icon not found.")
|
||||
val resource = iconResource()
|
||||
return resource.use { res ->
|
||||
Icon("image/png", 48, 48, 8, ICON_FILENAME, res).also {
|
||||
it.validate()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun iconResource(): InputStream {
|
||||
return MediaServer::class.java.getResourceAsStream("/$ICON_FILENAME")
|
||||
?: throw IllegalStateException("Icon not found.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,117 +0,0 @@
|
|||
package net.schowek.nextclouddlna.dlna.transport
|
||||
|
||||
import com.sun.net.httpserver.HttpExchange
|
||||
import mu.KLogging
|
||||
import org.jupnp.model.message.*
|
||||
import org.jupnp.protocol.ProtocolFactory
|
||||
import org.jupnp.transport.spi.UpnpStream
|
||||
import org.jupnp.util.io.IO
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import java.net.HttpURLConnection
|
||||
|
||||
|
||||
abstract class MyHttpExchangeUpnpStream(
|
||||
protocolFactory: ProtocolFactory,
|
||||
private val httpExchange: HttpExchange
|
||||
) : UpnpStream(protocolFactory) {
|
||||
|
||||
override fun run() {
|
||||
try {
|
||||
// Status
|
||||
val requestMessage = StreamRequestMessage(
|
||||
UpnpRequest.Method.getByHttpName(httpExchange.requestMethod),
|
||||
httpExchange.requestURI
|
||||
)
|
||||
if (requestMessage.operation.method == UpnpRequest.Method.UNKNOWN) {
|
||||
logger.warn("Method not supported by UPnP stack: {}", httpExchange.requestMethod)
|
||||
throw RuntimeException("Method not supported: {}" + httpExchange.requestMethod)
|
||||
}
|
||||
|
||||
// Protocol
|
||||
requestMessage.operation.httpMinorVersion = if (httpExchange.protocol.uppercase() == "HTTP/1.1") 1 else 0
|
||||
// Connection wrapper
|
||||
requestMessage.connection = createConnection()
|
||||
// Headers
|
||||
requestMessage.headers = UpnpHeaders(httpExchange.requestHeaders)
|
||||
|
||||
// Body
|
||||
val bodyBytes: ByteArray
|
||||
var inputStream: InputStream? = null
|
||||
try {
|
||||
inputStream = httpExchange.requestBody
|
||||
bodyBytes = IO.readBytes(inputStream)
|
||||
} finally {
|
||||
inputStream?.close()
|
||||
}
|
||||
logger.info(" Reading request body bytes: " + bodyBytes.size)
|
||||
if (bodyBytes.isNotEmpty() && requestMessage.isContentTypeMissingOrText) {
|
||||
logger.debug("Request contains textual entity body, converting then setting string on message")
|
||||
requestMessage.setBodyCharacters(bodyBytes)
|
||||
} else if (bodyBytes.isNotEmpty()) {
|
||||
logger.debug("Request contains binary entity body, setting bytes on message")
|
||||
requestMessage.setBody(UpnpMessage.BodyType.BYTES, bodyBytes)
|
||||
} else {
|
||||
logger.debug("Request did not contain entity body")
|
||||
}
|
||||
if (bodyBytes.isNotEmpty()) {
|
||||
logger.debug(" Request body: " + requestMessage.body)
|
||||
}
|
||||
val responseMessage = process(requestMessage)
|
||||
|
||||
// Return the response
|
||||
if (responseMessage != null) {
|
||||
// Headers
|
||||
httpExchange.responseHeaders.putAll(responseMessage.headers)
|
||||
|
||||
// Body
|
||||
val responseBodyBytes = if (responseMessage.hasBody()) responseMessage.bodyBytes else null
|
||||
val contentLength = responseBodyBytes?.size ?: -1
|
||||
logger.info("Sending HTTP response message: $responseMessage with content length: $contentLength")
|
||||
httpExchange.sendResponseHeaders(responseMessage.operation.statusCode, contentLength.toLong())
|
||||
if (responseBodyBytes!!.isNotEmpty()) {
|
||||
logger.debug(" Response body: " + responseMessage.body)
|
||||
}
|
||||
if (contentLength > 0) {
|
||||
logger.debug("Response message has body, writing bytes to stream...")
|
||||
var outputStream: OutputStream? = null
|
||||
try {
|
||||
outputStream = httpExchange.responseBody
|
||||
IO.writeBytes(outputStream, responseBodyBytes)
|
||||
outputStream.flush()
|
||||
} finally {
|
||||
outputStream?.close()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If it's null, it's 404, everything else needs a proper httpResponse
|
||||
logger.info("Sending HTTP response status: " + HttpURLConnection.HTTP_NOT_FOUND)
|
||||
httpExchange.sendResponseHeaders(HttpURLConnection.HTTP_NOT_FOUND, -1)
|
||||
}
|
||||
responseSent(responseMessage)
|
||||
} catch (t: Throwable) {
|
||||
// You definitely want to catch all Exceptions here, otherwise the server will
|
||||
// simply close the socket and you get an "unexpected end of file" on the client.
|
||||
// The same is true if you just rethrow an IOException - it is a mystery why it
|
||||
// is declared then on the HttpHandler interface if it isn't handled in any
|
||||
// way... so we always do error handling here.
|
||||
|
||||
// TODO: We should only send an error if the problem was on our side
|
||||
// You don't have to catch Throwable unless, like we do here in unit tests,
|
||||
// you might run into Errors as well (assertions).
|
||||
logger.warn("Exception occurred during UPnP stream processing:", t)
|
||||
try {
|
||||
httpExchange.sendResponseHeaders(HttpURLConnection.HTTP_INTERNAL_ERROR, -1)
|
||||
} catch (ex: IOException) {
|
||||
logger.warn("Couldn't send error response: ", ex)
|
||||
}
|
||||
responseException(t)
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract fun createConnection(): Connection?
|
||||
|
||||
companion object: KLogging()
|
||||
}
|
||||
|
|
@ -1,104 +1,37 @@
|
|||
package net.schowek.nextclouddlna.dlna.transport
|
||||
|
||||
import com.sun.net.httpserver.HttpExchange
|
||||
import com.sun.net.httpserver.HttpHandler
|
||||
import com.sun.net.httpserver.HttpServer
|
||||
import mu.KLogging
|
||||
import org.jupnp.model.message.Connection
|
||||
import org.jupnp.transport.Router
|
||||
import org.jupnp.transport.spi.InitializationException
|
||||
import org.jupnp.transport.spi.StreamServer
|
||||
import java.io.IOException
|
||||
import java.net.InetAddress
|
||||
import java.net.InetSocketAddress
|
||||
|
||||
|
||||
class MyStreamServerImpl(
|
||||
private val configuration: MyStreamServerConfiguration
|
||||
) : StreamServer<MyStreamServerConfiguration> {
|
||||
private var server: HttpServer? = null
|
||||
|
||||
@Synchronized
|
||||
@Throws(InitializationException::class)
|
||||
override fun init(bindAddress: InetAddress, router: Router) {
|
||||
try {
|
||||
val socketAddress = InetSocketAddress(bindAddress, configuration.listenPort)
|
||||
server = HttpServer.create(socketAddress, configuration.tcpConnectionBacklog)
|
||||
server!!.createContext("/", MyRequestHttpHandler(router))
|
||||
logger.info("* * * * * * * * * * * * * * * * * * * * * * * * * * * * * *")
|
||||
logger.info("Created server (for receiving TCP streams) on: " + server!!.address)
|
||||
logger.info("* * * * * * * * * * * * * * * * * * * * * * * * * * * * * *")
|
||||
} catch (ex: Exception) {
|
||||
throw InitializationException("Could not initialize " + javaClass.simpleName + ": " + ex.toString(), ex)
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
override fun getPort(): Int {
|
||||
return server!!.address.port
|
||||
return configuration.listenPort;
|
||||
}
|
||||
|
||||
override fun getConfiguration(): MyStreamServerConfiguration {
|
||||
return configuration
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
override fun run() {
|
||||
logger.info("Starting StreamServer...")
|
||||
// Starts a new thread but inherits the properties of the calling thread
|
||||
server!!.start()
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
override fun stop() {
|
||||
logger.info("Stopping StreamServer...")
|
||||
if (server != null) {
|
||||
server!!.stop(1)
|
||||
}
|
||||
}
|
||||
|
||||
inner class MyRequestHttpHandler(private val router: Router) : HttpHandler {
|
||||
// This is executed in the request receiving thread!
|
||||
@Throws(IOException::class)
|
||||
override fun handle(httpExchange: HttpExchange) {
|
||||
// And we pass control to the service, which will (hopefully) start a new thread immediately so we can
|
||||
// continue the receiving thread ASAP
|
||||
logger.info("Received HTTP exchange: " + httpExchange.requestMethod + " " + httpExchange.requestURI + " from " + httpExchange.remoteAddress)
|
||||
router.received(
|
||||
object : MyHttpExchangeUpnpStream(router.protocolFactory, httpExchange) {
|
||||
override fun createConnection(): Connection {
|
||||
return MyHttpServerConnection(httpExchange)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs a warning and returns `true`, we can't access the socket using the awful JDK webserver API.
|
||||
* Override this method if you know how to do it.
|
||||
*/
|
||||
private fun isConnectionOpen(exchange: HttpExchange?): Boolean {
|
||||
logger.warn("Can't check client connection, socket access impossible on JDK webserver!")
|
||||
return true
|
||||
}
|
||||
|
||||
private inner class MyHttpServerConnection(
|
||||
private val exchange: HttpExchange
|
||||
) : Connection {
|
||||
override fun isOpen(): Boolean {
|
||||
return isConnectionOpen(exchange)
|
||||
}
|
||||
|
||||
override fun getRemoteAddress(): InetAddress? {
|
||||
return if (exchange.remoteAddress != null) exchange.remoteAddress.address else null
|
||||
}
|
||||
|
||||
override fun getLocalAddress(): InetAddress? {
|
||||
return if (exchange.localAddress != null) exchange.localAddress.address else null
|
||||
}
|
||||
}
|
||||
|
||||
companion object: KLogging()
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue