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