OSDN Git Service

1c7f3947c2eb50d5e71242754a1c0c8c628626bc
[kancollesniffer/KancolleSniffer.git] / KancolleSniffer / LogServer.cs
1 // Copyright (C) 2014, 2015 Kazuhiro Fujieda <fujieda@users.osdn.me>\r
2 //\r
3 // Licensed under the Apache License, Version 2.0 (the "License");\r
4 // you may not use this file except in compliance with the License.\r
5 // You may obtain a copy of the License at\r
6 //\r
7 //    http://www.apache.org/licenses/LICENSE-2.0\r
8 //\r
9 // Unless required by applicable law or agreed to in writing, software\r
10 // distributed under the License is distributed on an "AS IS" BASIS,\r
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
12 // See the License for the specific language governing permissions and\r
13 // limitations under the License.\r
14 \r
15 using System;\r
16 using System.Collections.Generic;\r
17 using System.Globalization;\r
18 using System.IO;\r
19 using System.Linq;\r
20 using System.Net;\r
21 using System.Net.Sockets;\r
22 using System.Text;\r
23 using System.Threading;\r
24 using System.Web;\r
25 \r
26 namespace KancolleSniffer\r
27 {\r
28     public class LogServer\r
29     {\r
30         private readonly TcpListener _listener;\r
31         private readonly string _indexDir = AppDomain.CurrentDomain.BaseDirectory;\r
32         private string _outputDir = AppDomain.CurrentDomain.BaseDirectory;\r
33         private readonly List<Socket> _sockets = new List<Socket>();\r
34 \r
35         public int Port { get; private set; }\r
36 \r
37         public bool IsListening { get; private set; }\r
38 \r
39         public string OutputDir\r
40         {\r
41             set { _outputDir = value; }\r
42         }\r
43 \r
44         public LogServer(int port)\r
45         {\r
46             _listener = new TcpListener(IPAddress.Loopback, port);\r
47         }\r
48 \r
49         public void Start()\r
50         {\r
51             _listener.Start();\r
52             Port = ((IPEndPoint)_listener.LocalEndpoint).Port;\r
53             IsListening = true;\r
54             new Thread(Listen).Start();\r
55         }\r
56 \r
57         private void Listen()\r
58         {\r
59             try\r
60             {\r
61                 while (true)\r
62                 {\r
63                     var socket = _listener.AcceptSocket();\r
64                     lock (_sockets)\r
65                         _sockets.Add(socket);\r
66                     new Thread(Process).Start(socket);\r
67                 }\r
68             }\r
69             catch (SocketException)\r
70             {\r
71             }\r
72             finally\r
73             {\r
74                 _listener.Stop();\r
75             }\r
76         }\r
77 \r
78         private void Process(Object obj)\r
79         {\r
80             var client = (Socket)obj;\r
81             var data = new byte[4096];\r
82             var from = DateTime.MinValue;\r
83             var to = DateTime.MaxValue;\r
84             try\r
85             {\r
86                 if (client.Receive(data) == 0)\r
87                     return;\r
88                 var request = Encoding.UTF8.GetString(data).Split('\r')[0].Split(' ');\r
89                 if (request.Length != 3)\r
90                 {\r
91                     SendError(client, "400 Bad Request");\r
92                     return;\r
93                 }\r
94                 if (!request[0].StartsWith("GET", StringComparison.OrdinalIgnoreCase))\r
95                 {\r
96                     SendError(client, "501 Not Implemented");\r
97                     return;\r
98                 }\r
99                 var tmp = request[1].Split('?');\r
100                 var path = HttpUtility.UrlDecode(tmp[0]);\r
101                 if (path == null || !path.StartsWith("/"))\r
102                 {\r
103                     SendError(client, "400 Bad Request");\r
104                     return;\r
105                 }\r
106                 if (tmp.Length == 2)\r
107                 {\r
108                     var query = HttpUtility.ParseQueryString(tmp[1]);\r
109                     if (query["from"] != null)\r
110                     {\r
111                         double tick;\r
112                         double.TryParse(query["from"], out tick);\r
113                         from = new DateTime(1970, 1, 1).ToLocalTime().AddSeconds(tick / 1000);\r
114                     }\r
115                     if (query["to"] != null)\r
116                     {\r
117                         double tick;\r
118                         double.TryParse(query["to"], out tick);\r
119                         to = new DateTime(1970, 1, 1).ToLocalTime().AddSeconds(tick / 1000);\r
120                     }\r
121                 }\r
122 \r
123                 path = path == "/" ? "index.html" : path.Substring(1);\r
124                 var full = Path.Combine(_indexDir, path);\r
125                 var csv = Path.Combine(_outputDir, path);\r
126                 if (path.EndsWith(".html", StringComparison.OrdinalIgnoreCase) && File.Exists(full))\r
127                 {\r
128                     SendFile(client, full, "text/html");\r
129                     return;\r
130                 }\r
131                 if (path.EndsWith(".csv", StringComparison.OrdinalIgnoreCase) && File.Exists(csv))\r
132                 {\r
133                     SendFile(client, csv, "text/csv; charset=Shift_JIS");\r
134                     return;\r
135                 }\r
136                 if (path.EndsWith(".json", StringComparison.OrdinalIgnoreCase))\r
137                 {\r
138                     SendJsonData(client, csv, from, to);\r
139                     return;\r
140                 }\r
141                 if (path.EndsWith(".js", StringComparison.OrdinalIgnoreCase) && File.Exists(full))\r
142                 {\r
143                     SendFile(client, full, "application/javascript");\r
144                     return;\r
145                 }\r
146                 SendError(client, "404 Not Found");\r
147             }\r
148             catch (IOException)\r
149             {\r
150             }\r
151             catch (SocketException)\r
152             {\r
153             }\r
154             finally\r
155             {\r
156                 lock (_sockets)\r
157                     _sockets.Remove(client);\r
158                 client.Close();\r
159             }\r
160         }\r
161 \r
162         private void SendError(Socket client, string error)\r
163         {\r
164             using (var writer = new StreamWriter(new MemoryStream(), Encoding.ASCII))\r
165             {\r
166                 writer.Write("HTTP/1.1 {0}\r\n", error);\r
167                 writer.Write("Server: KancolleSniffer\r\n");\r
168                 writer.Write("Date: {0:R}\r\n", DateTime.Now);\r
169                 writer.Write("Connection: close\r\n\r\n");\r
170                 writer.Write("<html><head><title>{0}</title></head>\r\n", error);\r
171                 writer.Write("<body><h4>{0}</h4></body></html>\r\n\r\n", error);\r
172                 writer.Flush();\r
173                 client.Send(((MemoryStream)writer.BaseStream).ToArray());\r
174             }\r
175         }\r
176 \r
177         private void SendJsonData(Socket client, string path, DateTime from, DateTime to)\r
178         {\r
179             var header = new StreamWriter(new MemoryStream(), Encoding.ASCII);\r
180             header.Write("HTTP/1.1 200 OK\r\n");\r
181             header.Write("Server: KancolleSniffer\r\n");\r
182             header.Write("Date: {0:R}\r\n", DateTime.Now);\r
183             header.Write("Content-Type: {0}\r\n", "application/json; charset=Shift_JIS");\r
184             header.Write("Connection: close\r\n\r\n");\r
185             header.Flush();\r
186             client.Send(((MemoryStream)header.BaseStream).ToArray());\r
187 \r
188             var csv = path.Replace(".json", ".csv");\r
189             var encoding = Encoding.GetEncoding("Shift_JIS");\r
190             client.Send(encoding.GetBytes("{ \"data\": [\n"));\r
191             try\r
192             {\r
193                 if (File.Exists(csv))\r
194                 {\r
195                     var delimiter = "";\r
196                     var material = path.EndsWith("資材ログ.json"); // 末尾の空データを削除する必要がある\r
197                     foreach (var line in File.ReadLines(csv, encoding).Skip(1))\r
198                     {\r
199                         var data = line.Split(',');\r
200                         DateTime date;\r
201                         if (!DateTime.TryParseExact(data[0], Logger.DateTimeFormat, CultureInfo.InvariantCulture,\r
202                             DateTimeStyles.AssumeLocal, out date) &&\r
203                             DateTime.TryParse(data[0], CultureInfo.CurrentCulture, DateTimeStyles.AssumeLocal, out date))\r
204                         {\r
205                             data[0] = date.ToString(Logger.DateTimeFormat);\r
206                         }\r
207                         if (date < from || to < date)\r
208                             continue;\r
209                         client.Send(encoding.GetBytes(delimiter + "[\"" +\r
210                                                       string.Join("\",\"", (material ? data.Take(9) : data)) + "\"]"));\r
211                         delimiter = ",\n";\r
212                     }\r
213                 }\r
214             }\r
215             finally\r
216             {\r
217                 client.Send(encoding.GetBytes("]}\n"));\r
218             }\r
219         }\r
220 \r
221         private void SendFile(Socket client, string path, string mime)\r
222         {\r
223             using (var writer = new StreamWriter(new MemoryStream(), Encoding.ASCII))\r
224             {\r
225                 writer.Write("HTTP/1.1 200 OK\r\n");\r
226                 writer.Write("Server: KancolleSniffer\r\n");\r
227                 writer.Write("Date: {0:R}\r\n", DateTime.Now);\r
228                 writer.Write("Content-Length: {0}\r\n", new FileInfo(path).Length);\r
229                 writer.Write("Content-Type: {0}\r\n", mime);\r
230                 writer.Write("Connection: close\r\n\r\n");\r
231                 writer.Flush();\r
232                 client.SendFile(path, ((MemoryStream)writer.BaseStream).ToArray(), null,\r
233                     TransmitFileOptions.UseDefaultWorkerThread);\r
234             }\r
235         }\r
236 \r
237         public void Stop()\r
238         {\r
239             IsListening = false;\r
240             _listener.Server.Close();\r
241             lock (_sockets)\r
242                 _sockets.ForEach(s => s.Close());\r
243         }\r
244     }\r
245 }