1 //
2 // Copyright (c) 2010-2023 Antmicro
3 //
4 // This file is licensed under the MIT License.
5 // Full license text is available in 'licenses/MIT.txt'.
6 //
7 using System;
8 using System.IO;
9 using System.Net;
10 using System.Net.Sockets;
11 using System.Threading;
12 using Antmicro.Renode.Logging;
13 using Antmicro.Renode.Utilities;
14 
15 namespace Antmicro.Renode.RobotFramework
16 {
17     internal class HttpServer : IDisposable
18     {
HttpServer(XmlRpcServer processor)19         public HttpServer(XmlRpcServer processor)
20         {
21             xmlRpcServer = processor;
22             listenerThread = new Thread(Runner)
23             {
24                 Name = "Robot Framework listener thread",
25                 IsBackground = true
26             };
27         }
28 
29         // port == 0 is special as it means "select any available port"
Run(int port)30         public void Run(int port)
31         {
32             var selectAnyPort = (port == 0);
33             // range 49152-65535 as suggested in https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.txt for Private Ports
34             var minPort = selectAnyPort ? 49152 : port;
35             var maxPort = selectAnyPort ? 65535 : port;
36 
37             if(!TryStartListener(out listener, out var actualPort, minPort, maxPort))
38             {
39                 Logger.Log(LogLevel.Error, "Could not start the HTTP server on {0}", selectAnyPort ? "any port" : $"port {port}");
40                 return;
41             }
42 
43             if(!TryCreatePortFile(actualPort))
44             {
45                 return;
46             }
47 
48             Logger.Log(LogLevel.Info, "Robot Framework remote server is listening on port {0}", actualPort);
49 
50             listenerThread.Start();
51             listenerThread.Join();
52         }
53 
TryCreatePortFile(int actualPort)54         private bool TryCreatePortFile(int actualPort)
55         {
56             if(!TemporaryFilesManager.Instance.TryCreateFile(RobotPortFile, out var file))
57             {
58                 Logger.Log(LogLevel.Error, "Could not create port file");
59                 return false;
60             }
61 
62             try
63             {
64                 File.WriteAllText(file, actualPort.ToString());
65             }
66             catch(Exception ex)
67             {
68                 Logger.Log(LogLevel.Error, "Could not create port file: {0}", ex.Message);
69                 return false;
70             }
71 
72             return true;
73         }
74 
TryStartListener(out HttpListener listener, out int port, int minPort, int maxPort)75         private bool TryStartListener(out HttpListener listener, out int port, int minPort, int maxPort)
76         {
77             for(port = minPort; port <= maxPort; port++)
78             {
79                 listener = new HttpListener();
80 #if PLATFORM_WINDOWS
81                 listener.Prefixes.Add($"http://localhost:{port}/");
82 #else
83                 listener.Prefixes.Add($"http://*:{port}/");
84 #endif
85                 try
86                 {
87                     listener.Start();
88                     return true;
89                 }
90                 catch(SocketException)
91                 {
92                     // let's try the next port
93                     continue;
94                 }
95                 catch(HttpListenerException)
96                 {
97                     // let's try the next port under .NET Framework
98                     continue;
99                 }
100             }
101 
102             listener = null;
103             return false;
104         }
105 
Shutdown()106         public void Shutdown()
107         {
108             quit = true;
109             if(Thread.CurrentThread != listenerThread)
110             {
111                 listener?.Close();
112                 listenerThread.Join();
113             }
114         }
115 
Dispose()116         public void Dispose()
117         {
118             xmlRpcServer.Dispose();
119         }
120 
121         public XmlRpcServer Processor { get { return xmlRpcServer; } }
122 
Runner()123         private void Runner()
124         {
125             while(!quit)
126             {
127                 var context = listener.GetContext();
128                 xmlRpcServer.ProcessRequest(context);
129             }
130             Logger.Log(LogLevel.Info, "Robot Framework remote servers listener thread stopped");
131         }
132 
133         private volatile bool quit;
134         private HttpListener listener;
135         private readonly Thread listenerThread;
136         private readonly XmlRpcServer xmlRpcServer;
137 
138         private const string RobotPortFile = "robot_port";
139     }
140 }
141 
142