930efcc258923c0f9178647b516bc663cddd8cca
[idea/community.git] / update-server-mock / src / main / java / com / intellij / updater / mock / Server.kt
1 /*
2  * Copyright (c) 2016 JetBrains s.r.o.
3  *
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
7  *
8  *   http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 package com.intellij.updater.mock
17
18 import com.sun.net.httpserver.HttpServer
19 import java.net.HttpURLConnection.*
20 import java.net.InetSocketAddress
21 import java.net.URI
22 import java.time.ZonedDateTime
23 import java.time.format.DateTimeFormatter
24
25 class Server(private val port: Int, private val generator: Generator) {
26   private val server = HttpServer.create()
27   private val buildFormat = "([A-Z]+)-([0-9.]+)".toRegex()
28   private val tsFormat = DateTimeFormatter.ofPattern("dd/MMM/yyyy:kk:mm:ss ZZ")
29
30   fun start() {
31     server.bind(InetSocketAddress("localhost", port), 0)
32
33     server.createContext("/") { ex ->
34       val response = try {
35         process(ex.requestMethod, ex.requestURI)
36       }
37       catch(e: Exception) {
38         e.printStackTrace()
39         Response(HTTP_INTERNAL_ERROR, "Internal error")
40       }
41
42       val contentType = if (response.type.startsWith("text/")) response.type + "; charset=utf-8" else response.type
43       ex.responseHeaders.add("Content-Type", contentType)
44       ex.sendResponseHeaders(response.code, response.bytes.size.toLong())
45       ex.responseBody.write(response.bytes)
46       ex.close()
47
48       println("${ex.remoteAddress.address.hostAddress} - - [${tsFormat.format(ZonedDateTime.now())}]" +
49         " \"${ex.requestMethod} ${ex.requestURI}\" ${ex.responseCode} ${response.bytes.size}")
50     }
51
52     server.start()
53   }
54
55   private fun process(method: String, uri: URI): Response {
56     val path = uri.path
57     return when {
58       method != "GET" -> Response(HTTP_BAD_REQUEST, "Didn't get")
59       path == "/" -> Response(HTTP_OK, "Mock Update Server")
60       path == "/updates/updates.xml" -> xml(uri.query ?: "")
61       path.startsWith("/patches/") -> patch(path)
62       else -> Response(HTTP_NOT_FOUND, "Miss")
63     }
64   }
65
66   private fun xml(query: String): Response {
67     val parameters = query.splitToSequence('&')
68       .filter { it.startsWith("build") || it.startsWith("eap") }
69       .map { it.split('=', limit = 2) }
70       .map { it[0] to if (it.size > 1) it[1] else "" }
71       .toMap()
72
73     val build = parameters["build"]
74     if (build != null) {
75       val match = buildFormat.find(build)
76       val productCode = match?.groups?.get(1)?.value
77       val buildId = match?.groups?.get(2)?.value
78       if (productCode != null && buildId != null) {
79         val xml = generator.generateXml(productCode, buildId, "eap" in parameters)
80         return Response(HTTP_OK, "text/xml", xml.toByteArray())
81       }
82     }
83
84     return Response(HTTP_BAD_REQUEST, "Bad parameters")
85   }
86
87   private fun patch(path: String): Response = when {
88     path.endsWith(".jar") -> Response(HTTP_OK, "binary/octet-stream", generator.generatePatch())
89     else -> Response(HTTP_BAD_REQUEST, "Bad path")
90   }
91
92   private class Response(val code: Int, val type: String, val bytes: ByteArray) {
93     constructor(code: Int, text: String) : this(code, "text/plain", text.toByteArray())
94   }
95 }