-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathQueryServerMain.cs
More file actions
196 lines (180 loc) · 7 KB
/
QueryServerMain.cs
File metadata and controls
196 lines (180 loc) · 7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
using LabApi.Features;
using LabApi.Features.Console;
using LabApi.Features.Wrappers;
using LabApi.Loader;
using LabApi.Loader.Features.Plugins;
using System;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
namespace LabAPI_QueryServer;
/// <summary>
/// 查询服务器 插件
/// </summary>
public class QueryServerMain : Plugin {
// HttpListener 实例
private HttpListener Listener { get; set; }
// Http 监听地址
private static string HttpAddress { get; set; }
// 取消令牌源
private CancellationTokenSource _cts;
// JSON 序列化选项
private static readonly JsonSerializerOptions JsonOptions = new() { Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping };
/// <summary>
/// 单例模式。
/// </summary>
public static QueryServerMain MainSingleton { get; private set; }
/// <summary>
/// 插件名称。
/// </summary>
public override string Name => "Query Server Plugin";
/// <summary>
/// 插件描述。
/// </summary>
public override string Description => "Launches a local HTTP query server for querying game round information.";
/// <summary>
/// 插件作者。
/// </summary>
public override string Author => "TASA-Ed Studio";
/// <summary>
/// 插件版本。
/// </summary>
public override Version Version => new(1, 1, 0, 0);
/// <summary>
/// 需要的 LabApi 版本。
/// </summary>
public override Version RequiredApiVersion => new(LabApiProperties.CompiledVersion);
/// <summary>
/// 插件配置。
/// </summary>
public QueryServerConfig Config { get; private set; }
// 加载配置时
public override void LoadConfigs() {
base.LoadConfigs();
Config = this.LoadConfig<QueryServerConfig>("configs.yml") ?? new QueryServerConfig();
HttpAddress = $"http://{Config.QueryServerHost}:{(Config.QueryServerPort == 0 ? Server.Port : Config.QueryServerPort)}/";
}
// 启用插件时
public override void Enable() {
MainSingleton = this;
// 启动 HttpListener
_cts = new CancellationTokenSource();
Listener = new HttpListener();
Listener.Prefixes.Add(HttpAddress);
Listener.Start();
Logger.Info($"The query server is listening at `{HttpAddress}` ...");
// 开始接受客户端连接
_ = AcceptClientAsync(_cts.Token);
}
// 禁用插件时
public override void Disable() {
_cts?.Cancel();
Listener.Stop();
Listener.Close();
Config = null;
MainSingleton = null;
Logger.Info("The query server has been stopped.");
}
// 接受客户端连接
private async Task AcceptClientAsync(CancellationToken cancellationToken) {
try {
var context = await Listener.GetContextAsync();
if (MainSingleton.Config.QueryServerDebug) Logger.Info($"Request from {context.Request.RemoteEndPoint}");
// 立即开始下一个监听(递归调用)
_ = AcceptClientAsync(cancellationToken);
if (MainSingleton.Config.QueryServerDebug) Logger.Info("Listening for next request...");
// 处理当前请求
await HandleRequestAsync(context);
} catch (HttpListenerException) when (cancellationToken.IsCancellationRequested) {
// 正常停止
} catch (Exception ex) {
Logger.Error($"Error: {ex.Message}");
// 出错后继续监听
if (!cancellationToken.IsCancellationRequested) _ = AcceptClientAsync(cancellationToken);
}
}
// 处理请求
private static async Task HandleRequestAsync(HttpListenerContext context) {
try {
// 构造响应内容
object responseObj;
if (ServerTime.time == 0) {
// 有些属性只能在服务器完全启动后获取
responseObj = new {
data = "The server has not yet fully started. Please try again later.",
success = false
};
} else {
// 获取玩家列表
var playerList = Player.ReadyList.ToList();
object[] list = [];
if (playerList.Any())
list = playerList.Select(p => new {
name = p.Nickname,
is_admin = p.RemoteAdminAccess,
is_dummy = p.IsDummy
}).ToArray<object>();
// 构造响应对象
responseObj = new {
// 服务器信息
name = Server.ServerListName,
port = Server.Port,
// 玩家信息
player = new {
online = Server.PlayerCount,
max = Server.MaxPlayers,
list,
admins = playerList.Count(p => p.RemoteAdminAccess)
},
// 秒,空闲模式时不增加
ServerTime.time,
// 回合信息
friendly_fire = Server.FriendlyFire,
warhead = Warhead.IsDetonated,
decontamination = Decontamination.IsDecontaminating,
success = true
};
}
var response = JsonSerializer.Serialize(responseObj, JsonOptions);
var buffer = Encoding.UTF8.GetBytes(response);
// 设置响应头
context.Response.ContentType = "application/json; charset=utf-8";
context.Response.ContentEncoding = Encoding.UTF8;
context.Response.ContentLength64 = buffer.Length;
context.Response.StatusCode = 200;
// 允许跨域
context.Response.Headers.Add("Access-Control-Allow-Origin", "*");
// 发送响应
await context.Response.OutputStream.WriteAsync(buffer, 0, buffer.Length);
if (MainSingleton.Config.QueryServerDebug) Logger.Info($"Response sent: {response}");
} catch (Exception ex) {
// 处理错误
context.Response.StatusCode = 500;
Logger.Error($"Handler error: {ex.Message}");
} finally {
context.Response.Close();
}
}
}
/// <summary>
/// 插件配置类。
/// </summary>
public class QueryServerConfig {
// 所有配置必须为可 set
/// <summary>
/// HttpListener 监听的端口,为 0 时默认监听 LocalAdmin (服务端)的端口。
/// </summary>
public ushort QueryServerPort { get; set; } = 0;
/// <summary>
/// HttpListener 监听的地址,监听所有时应设置为 * 。
/// </summary>
public string QueryServerHost { get; set; } = "127.0.0.1";
/// <summary>
/// Debug 模式,开启后会输出每个请求的信息。
/// </summary>
public bool QueryServerDebug { get; set; } = false;
}