2 * Copyright (c) 2016 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.intellij.updater.mock
18 import com.sun.net.httpserver.Filter
19 import com.sun.net.httpserver.HttpExchange
20 import com.sun.net.httpserver.HttpHandler
21 import com.sun.net.httpserver.HttpServer
22 import java.io.OutputStream
23 import java.net.HttpURLConnection.HTTP_BAD_REQUEST
24 import java.net.HttpURLConnection.HTTP_OK
25 import java.net.InetSocketAddress
26 import java.time.ZonedDateTime
27 import java.time.format.DateTimeFormatter
29 class Server(private val port: Int, private val generator: Generator) {
30 private val server = HttpServer.create()
33 server.bind(InetSocketAddress("localhost", port), 0)
34 server.handle("/updates/updates.xml", HttpHandler { sendUpdatesXml(it) })
35 server.handle("/patches/", HttpHandler { sendPatch(it) })
36 server.handle("/", HttpHandler { sendText(it, "Mock Update Server") })
40 private fun sendText(ex: HttpExchange, data: String, type: String = "text/plain", code: Int = HTTP_OK) {
41 val bytes = data.toByteArray()
42 ex.responseHeaders.add("Content-Type", "$type; charset=utf-8")
43 ex.sendResponseHeaders(code, bytes.size.toLong())
44 ex.responseBody.write(bytes)
48 private fun sendUpdatesXml(ex: HttpExchange) {
49 var build: String? = null
51 ex.requestURI.query?.splitToSequence('&')?.forEach {
52 val p = it.split('=', limit = 2)
54 "build" -> build = if (p.size > 1) p[1] else null
59 sendText(ex, "Parameter missing", code = HTTP_BAD_REQUEST)
63 val result = "([A-Z]+)-([0-9.]+)".toRegex().find(build!!)
64 val productCode = result?.groups?.get(1)?.value
65 val buildId = result?.groups?.get(2)?.value
66 if (productCode == null || buildId == null) {
67 sendText(ex, "Parameter malformed", code = HTTP_BAD_REQUEST)
71 val xml = generator.generateXml(productCode, buildId, eap)
72 sendText(ex, xml, "text/xml")
75 private fun sendPatch(ex: HttpExchange) {
76 if (!ex.requestURI.path.endsWith(".jar")) {
77 sendText(ex, "Request malformed", code = HTTP_BAD_REQUEST)
81 val patch = generator.generatePatch()
82 ex.responseHeaders.add("Content-Type", "binary/octet-stream")
83 ex.sendResponseHeaders(HTTP_OK, patch.size.toLong())
84 ex.responseBody.write(patch)
89 private fun HttpServer.handle(path: String, handler: HttpHandler) {
90 val ctx = createContext(path, handler)
91 ctx.filters += AccessLogFilter()
94 private class AccessLogFilter : Filter() {
96 private val DTF = DateTimeFormatter.ofPattern("dd/MMM/yyyy:kk:mm:ss ZZ")
99 override fun description() = "Access Log Filter"
101 override fun doFilter(ex: HttpExchange, chain: Chain) {
102 val out = CountingOutputStream(ex.responseBody)
103 ex.setStreams(ex.requestBody, out)
107 println("${ex.remoteAddress.address.hostAddress} - - [${DTF.format(ZonedDateTime.now())}]" +
108 " \"${ex.requestMethod} ${ex.requestURI}\" ${ex.responseCode} ${out.count}")
110 catch(e: Exception) {
117 private class CountingOutputStream(private val stream: OutputStream) : OutputStream() {
119 private set(v) { field = v }
121 override fun write(b: Int) {
126 override fun write(b: ByteArray, off: Int, len: Int) {
127 stream.write(b, off, len)
131 override fun flush() = stream.flush()
133 override fun close() = stream.close()