473 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			SourcePawn
		
	
	
	
	
	
			
		
		
	
	
			473 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			SourcePawn
		
	
	
	
	
	
| #pragma semicolon 1
 | |
| #pragma newdecls required
 | |
| #pragma dynamic 65535
 | |
| 
 | |
| #include <sourcemod>
 | |
| #include <AsyncSocket>
 | |
| #include <smjansson>
 | |
| 
 | |
| #define MAX_CLIENTS 16
 | |
| 
 | |
| #include "API.inc"
 | |
| #include "Subscribe.inc"
 | |
| 
 | |
| static AsyncSocket g_ServerSocket;
 | |
| 
 | |
| static AsyncSocket g_Client_Socket[MAX_CLIENTS] = { null, ... };
 | |
| static int g_Client_Subscriber[MAX_CLIENTS] = { -1, ... };
 | |
| 
 | |
| ConVar g_ListenAddr;
 | |
| ConVar g_ListenPort;
 | |
| 
 | |
| public Plugin myinfo =
 | |
| {
 | |
| 	name = "SM JSON API",
 | |
| 	author = "SourceMod TCP JSON API",
 | |
| 	description = "",
 | |
| 	version = "1.0",
 | |
| 	url = ""
 | |
| }
 | |
| 
 | |
| public void OnPluginStart()
 | |
| {
 | |
| 	Subscribe_OnPluginStart();
 | |
| 
 | |
| 	g_ListenAddr = CreateConVar("sm_jsonapi_addr", "127.0.0.1", "SM JSON API listen ip address", FCVAR_PROTECTED);
 | |
| 	g_ListenPort = CreateConVar("sm_jsonapi_port", "27021", "SM JSON API listen ip address", FCVAR_PROTECTED, true, 1025.0, true, 65535.0);
 | |
| 
 | |
| 	AutoExecConfig(true, "plugin.SMJSONAPI");
 | |
| }
 | |
| 
 | |
| public void OnConfigsExecuted()
 | |
| {
 | |
| 	if(g_ServerSocket)
 | |
| 		return;
 | |
| 
 | |
| 	g_ServerSocket = new AsyncSocket();
 | |
| 
 | |
| 	g_ServerSocket.SetConnectCallback(OnAsyncConnect);
 | |
| 	g_ServerSocket.SetErrorCallback(OnAsyncServerError);
 | |
| 
 | |
| 	char sAddr[32];
 | |
| 	g_ListenAddr.GetString(sAddr, sizeof(sAddr));
 | |
| 	int Port = g_ListenPort.IntValue;
 | |
| 
 | |
| 	g_ServerSocket.Listen(sAddr, Port);
 | |
| 	LogMessage("Listening on %s:%d", sAddr, Port);
 | |
| }
 | |
| 
 | |
| static void OnAsyncConnect(AsyncSocket socket)
 | |
| {
 | |
| 	int Client = GetFreeClientIndex();
 | |
| 	LogMessage("OnAsyncConnect(Client=%d)", Client);
 | |
| 
 | |
| 	if(Client == -1)
 | |
| 	{
 | |
| 		delete socket;
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	g_Client_Socket[Client] = socket;
 | |
| 	socket.SetErrorCallback(OnAsyncClientError);
 | |
| 	socket.SetDataCallback(OnAsyncData);
 | |
| }
 | |
| 
 | |
| static void OnAsyncServerError(AsyncSocket socket, int error, const char[] errorName)
 | |
| {
 | |
| 	SetFailState("OnAsyncServerError(): %d, \"%s\"", error, errorName);
 | |
| }
 | |
| 
 | |
| static void OnAsyncClientError(AsyncSocket socket, int error, const char[] errorName)
 | |
| {
 | |
| 	int Client = ClientFromSocket(socket);
 | |
| 	LogMessage("OnAsyncClientError(Client=%d): %d, \"%s\"", Client, error, errorName);
 | |
| 
 | |
| 	if(Client == -1)
 | |
| 	{
 | |
| 		delete socket;
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	if(g_Client_Subscriber[Client] != -1)
 | |
| 	{
 | |
| 		Subscriber_Destroy(g_Client_Subscriber[Client]);
 | |
| 		g_Client_Subscriber[Client] = -1;
 | |
| 	}
 | |
| 
 | |
| 	g_Client_Socket[Client] = null;
 | |
| 	delete socket;
 | |
| }
 | |
| 
 | |
| static void OnAsyncData(AsyncSocket socket, const char[] data, const int size)
 | |
| {
 | |
| 	int Client = ClientFromSocket(socket);
 | |
| 
 | |
| 	int iLine;
 | |
| 	int iColumn;
 | |
| 	static char sError[256];
 | |
| 	JSONValue jRequest = json_load_ex(data, sError, sizeof(sError), iLine, iColumn);
 | |
| 
 | |
| 	if(jRequest == null)
 | |
| 	{
 | |
| 		JSONObject jResponse = new JSONObject();
 | |
| 
 | |
| 		JSONObject jError = new JSONObject();
 | |
| 		jError.SetString("type", "json");
 | |
| 		jError.SetString("error", sError);
 | |
| 		jError.SetInt("line", iLine);
 | |
| 		jError.SetInt("column", iColumn);
 | |
| 
 | |
| 		jResponse.Set("error", jError);
 | |
| 
 | |
| 		jResponse.ToString(sError, sizeof(sError));
 | |
| 		socket.WriteNull(sError);
 | |
| 
 | |
| 		delete jResponse;
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	if(jRequest.IsObject)
 | |
| 	{
 | |
| 		//view_as<JSONObject>(jRequest).DumpToServer();
 | |
| 		JSONObject jResponse = new JSONObject();
 | |
| 		// Negative values and objects indicate errors
 | |
| 		// 0 and positive integers indicate success
 | |
| 		jResponse.SetInt("error", 0);
 | |
| 
 | |
| 		HandleRequest(Client, view_as<JSONObject>(jRequest), jResponse);
 | |
| 
 | |
| 		static char sResponse[4096];
 | |
| 		jResponse.ToString(sResponse, sizeof(sResponse));
 | |
| 		socket.WriteNull(sResponse);
 | |
| 		delete jResponse;
 | |
| 	}
 | |
| 
 | |
| 	delete jRequest;
 | |
| }
 | |
| 
 | |
| static int HandleRequest(int Client, JSONObject jRequest, JSONObject jResponse)
 | |
| {
 | |
| 	static char sMethod[32];
 | |
| 	if(jRequest.GetString("method", sMethod, sizeof(sMethod)) < 0)
 | |
| 	{
 | |
| 		JSONObject jError = new JSONObject();
 | |
| 		jError.SetString("error", "Request has no 'method' string-value.");
 | |
| 		jResponse.Set("error", jError);
 | |
| 		return -1;
 | |
| 	}
 | |
| 	jResponse.SetString("method", sMethod);
 | |
| 
 | |
| 	if(StrEqual(sMethod, "subscribe") || StrEqual(sMethod, "unsubscribe") || StrEqual(sMethod, "replay"))
 | |
| 	{
 | |
| 		int Subscriber = g_Client_Subscriber[Client];
 | |
| 		if(Subscriber == -1)
 | |
| 		{
 | |
| 			Subscriber = Subscriber_Create(Client);
 | |
| 			if(Subscriber < 0)
 | |
| 			{
 | |
| 				JSONObject jError = new JSONObject();
 | |
| 				jError.SetString("type", "subscribe");
 | |
| 				jError.SetString("error", "Could not allocate a subscriber.");
 | |
| 				jError.SetInt("code", Subscriber);
 | |
| 				jResponse.Set("error", jError);
 | |
| 				return -1;
 | |
| 			}
 | |
| 			g_Client_Subscriber[Client] = Subscriber;
 | |
| 		}
 | |
| 
 | |
| 		return Subscriber_HandleRequest(Subscriber, jRequest, jResponse);
 | |
| 	}
 | |
| 	else if(StrEqual(sMethod, "function"))
 | |
| 	{
 | |
| 		static char sFunction[64];
 | |
| 		if(jRequest.GetString("function", sFunction, sizeof(sFunction)) < 0)
 | |
| 		{
 | |
| 			JSONObject jError = new JSONObject();
 | |
| 			jError.SetString("error", "Request has no 'function' string-value.");
 | |
| 			jResponse.Set("error", jError);
 | |
| 			return -1;
 | |
| 		}
 | |
| 		jResponse.SetString("function", sFunction);
 | |
| 
 | |
| 		static char sAPIFunction[64];
 | |
| 		Format(sAPIFunction, sizeof(sAPIFunction), "API_%s", sFunction);
 | |
| 
 | |
| 		Function Fun = GetFunctionByName(INVALID_HANDLE, sAPIFunction);
 | |
| 		if(Fun == INVALID_FUNCTION)
 | |
| 		{
 | |
| 			int Res = Call_StartNative(sFunction);
 | |
| 			if(!Res)
 | |
| 			{
 | |
| 				JSONObject jError = new JSONObject();
 | |
| 				jError.SetString("error", "Invalid function specified.");
 | |
| 				jError.SetInt("code", Res);
 | |
| 				jResponse.Set("error", jError);
 | |
| 				return -1;
 | |
| 			}
 | |
| 		}
 | |
| 		else
 | |
| 			Call_StartFunction(INVALID_HANDLE, Fun);
 | |
| 
 | |
| 		JSONArray jArgsArray = view_as<JSONArray>(jRequest.Get("args"));
 | |
| 		if(jArgsArray == null || !jArgsArray.IsArray)
 | |
| 		{
 | |
| 			delete jArgsArray;
 | |
| 			Call_Cancel();
 | |
| 			JSONObject jError = new JSONObject();
 | |
| 			jError.SetString("error", "Request has no 'args' array-value.");
 | |
| 			jResponse.Set("error", jError);
 | |
| 			return -1;
 | |
| 		}
 | |
| 
 | |
| 		int aiValues[32];
 | |
| 		int iValues = 0;
 | |
| 
 | |
| 		float afValues[32];
 | |
| 		int fValues = 0;
 | |
| 
 | |
| 		static char asValues[16][1024];
 | |
| 		int sValues = 0;
 | |
| 
 | |
| 		for(int i = 0; i < jArgsArray.Length; i++)
 | |
| 		{
 | |
| 			bool Fail = false;
 | |
| 			JSONValue jValue;
 | |
| 			jValue = jArgsArray.Get(i);
 | |
| 
 | |
| 			char sType1[32];
 | |
| 			jValue.TypeToString(sType1, sizeof(sType1));
 | |
| 
 | |
| 			if(jValue.IsArray)
 | |
| 			{
 | |
| 				JSONArray jValueArray = view_as<JSONArray>(jValue);
 | |
| 				JSONValue jArrayValue = jValueArray.Get(0);
 | |
| 
 | |
| 				char sType2[32];
 | |
| 				jArrayValue.TypeToString(sType2, sizeof(sType2));
 | |
| 
 | |
| 				if(jArrayValue.IsInteger || jArrayValue.IsBoolean)
 | |
| 				{
 | |
| 					int iValues_ = iValues;
 | |
| 					for(int j = 0; j < jValueArray.Length; j++)
 | |
| 					{
 | |
| 						JSONInteger jInteger = view_as<JSONInteger>(jValueArray.Get(j));
 | |
| 						aiValues[iValues_++] = jInteger.Value;
 | |
| 						delete jInteger;
 | |
| 					}
 | |
| 
 | |
| 					Call_PushArrayEx(aiValues[iValues], jValueArray.Length, SM_PARAM_COPYBACK);
 | |
| 					iValues += jValueArray.Length;
 | |
| 				}
 | |
| 				else if(jArrayValue.IsFloat)
 | |
| 				{
 | |
| 					int fValues_ = fValues;
 | |
| 					for(int j = 0; j < jValueArray.Length; j++)
 | |
| 					{
 | |
| 						JSONFloat jFloat = view_as<JSONFloat>(jValueArray.Get(j));
 | |
| 						afValues[fValues_++] = jFloat.Value;
 | |
| 						delete jFloat;
 | |
| 					}
 | |
| 
 | |
| 					Call_PushArrayEx(afValues[fValues], jValueArray.Length, SM_PARAM_COPYBACK);
 | |
| 					fValues += jValueArray.Length;
 | |
| 				}
 | |
| 				/*else if(jArrayValue.IsString)
 | |
| 				{
 | |
| 					int sValues_ = sValues;
 | |
| 					for(int j = 0; j < jValueArray.Length; j++)
 | |
| 					{
 | |
| 						JSONString jString = view_as<JSONString>(jValueArray.Get(j));
 | |
| 						jString.GetString(asValues[sValues_++], sizeof(asValues[]));
 | |
| 						delete jString;
 | |
| 					}
 | |
| 
 | |
| 					Call_PushArrayEx(view_as<int>(asValues[sValues]), jValueArray.Length, SM_PARAM_COPYBACK);
 | |
| 					sValues += jValueArray.Length;
 | |
| 				}*/
 | |
| 				else if(jArrayValue.IsArray) // Special
 | |
| 				{
 | |
| 					static char sSpecial[32];
 | |
| 					view_as<JSONArray>(jArrayValue).GetString(0, sSpecial, sizeof(sSpecial));
 | |
| 
 | |
| 					if(StrEqual(sSpecial, "NULL_VECTOR"))
 | |
| 						Call_PushArrayEx(NULL_VECTOR, 3, 0);
 | |
| 					else if(StrEqual(sSpecial, "NULL_STRING"))
 | |
| 						Call_PushString(NULL_STRING);
 | |
| 					else
 | |
| 						Fail = true;
 | |
| 				}
 | |
| 				else
 | |
| 					Fail = true;
 | |
| 
 | |
| 				if(Fail)
 | |
| 				{
 | |
| 					Call_Cancel();
 | |
| 					delete jArrayValue;
 | |
| 					delete jValue;
 | |
| 					delete jArgsArray;
 | |
| 
 | |
| 					static char sType[16];
 | |
| 					jValue.TypeToString(sType, sizeof(sType));
 | |
| 
 | |
| 					char sError[128];
 | |
| 					FormatEx(sError, sizeof(sError), "Unsupported parameter in list %d of type '%s'", i, sType);
 | |
| 
 | |
| 					JSONObject jError = new JSONObject();
 | |
| 					jError.SetString("error", sError);
 | |
| 					jResponse.Set("error", jError);
 | |
| 					return -1;
 | |
| 				}
 | |
| 				delete jArrayValue;
 | |
| 			}
 | |
| 			else if(jValue.IsInteger || jValue.IsBoolean)
 | |
| 			{
 | |
| 				aiValues[iValues] = view_as<JSONInteger>(jValue).Value;
 | |
| 				Call_PushCell(aiValues[iValues++]);
 | |
| 			}
 | |
| 			else if(jValue.IsFloat)
 | |
| 			{
 | |
| 				afValues[fValues] = view_as<JSONFloat>(jValue).Value;
 | |
| 				Call_PushFloat(afValues[fValues++]);
 | |
| 			}
 | |
| 			else if(jValue.IsString)
 | |
| 			{
 | |
| 				// Broken: view_as<JSONString>(jValue).GetString(asValues[sValues], sizeof(asValues[]));
 | |
| 				JSONString jString = view_as<JSONString>(jValue);
 | |
| 				jString.GetString(asValues[sValues], sizeof(asValues[]));
 | |
| 				Call_PushStringEx(asValues[sValues++], sizeof(asValues[]), SM_PARAM_STRING_COPY, SM_PARAM_COPYBACK);
 | |
| 			}
 | |
| 			else
 | |
| 			{
 | |
| 				Call_Cancel();
 | |
| 				delete jValue;
 | |
| 				delete jArgsArray;
 | |
| 
 | |
| 				static char sType[16];
 | |
| 				jValue.TypeToString(sType, sizeof(sType));
 | |
| 
 | |
| 				char sError[128];
 | |
| 				FormatEx(sError, sizeof(sError), "Unsupported parameter %d of type '%s'", i, sType);
 | |
| 
 | |
| 				JSONObject jError = new JSONObject();
 | |
| 				jError.SetString("error", sError);
 | |
| 				jResponse.Set("error", jError);
 | |
| 				return -1;
 | |
| 			}
 | |
| 			delete jValue;
 | |
| 		}
 | |
| 
 | |
| 		int Result;
 | |
| 		static char sException[1024];
 | |
| 		int Error = Call_FinishEx(Result, sException, sizeof(sException));
 | |
| 
 | |
| 		if(Error != SP_ERROR_NONE)
 | |
| 		{
 | |
| 			delete jArgsArray;
 | |
| 			JSONObject jError = new JSONObject();
 | |
| 			jError.SetInt("error", Error);
 | |
| 			jError.SetString("exception", sException);
 | |
| 			jResponse.Set("error", jError);
 | |
| 			return -1;
 | |
| 		}
 | |
| 
 | |
| 		jResponse.SetInt("result", Result);
 | |
| 
 | |
| 		JSONArray jArgsResponse = new JSONArray();
 | |
| 		iValues = 0;
 | |
| 		fValues = 0;
 | |
| 		sValues = 0;
 | |
| 
 | |
| 		for(int i = 0; i < jArgsArray.Length; i++)
 | |
| 		{
 | |
| 			JSONValue jValue;
 | |
| 			jValue = jArgsArray.Get(i);
 | |
| 
 | |
| 			if(jValue.IsArray)
 | |
| 			{
 | |
| 				JSONArray jArrayResponse = new JSONArray();
 | |
| 				JSONArray jValueArray = view_as<JSONArray>(jValue);
 | |
| 				JSONValue jArrayValue = jValueArray.Get(0);
 | |
| 
 | |
| 				if(jArrayValue.IsInteger || jArrayValue.IsBoolean)
 | |
| 				{
 | |
| 					for(int j = 0; j < jValueArray.Length; j++)
 | |
| 						jArrayResponse.AppendInt(aiValues[iValues++]);
 | |
| 				}
 | |
| 				else if(jArrayValue.IsFloat)
 | |
| 				{
 | |
| 					for(int j = 0; j < jValueArray.Length; j++)
 | |
| 						jArrayResponse.AppendFloat(afValues[fValues++]);
 | |
| 				}
 | |
| 				else if(jArrayValue.IsString)
 | |
| 				{
 | |
| 					for(int j = 0; j < jValueArray.Length; j++)
 | |
| 						jArrayResponse.AppendString(asValues[sValues++]);
 | |
| 				}
 | |
| 				else if(jArrayValue.IsArray) // Special
 | |
| 				{
 | |
| 					static char sSpecial[32];
 | |
| 					view_as<JSONArray>(jArrayValue).GetString(0, sSpecial, sizeof(sSpecial));
 | |
| 					jArrayResponse.AppendString(sSpecial);
 | |
| 				}
 | |
| 				delete jArrayValue;
 | |
| 				jArgsResponse.Append(jArrayResponse);
 | |
| 			}
 | |
| 			else if(jValue.IsInteger || jValue.IsBoolean)
 | |
| 			{
 | |
| 				jArgsResponse.AppendInt(aiValues[iValues++]);
 | |
| 			}
 | |
| 			else if(jValue.IsFloat)
 | |
| 			{
 | |
| 				jArgsResponse.AppendFloat(afValues[fValues++]);
 | |
| 			}
 | |
| 			else if(jValue.IsString)
 | |
| 			{
 | |
| 				jArgsResponse.AppendString(asValues[sValues++]);
 | |
| 			}
 | |
| 			delete jValue;
 | |
| 		}
 | |
| 
 | |
| 		jResponse.Set("args", jArgsResponse);
 | |
| 
 | |
| 		delete jArgsArray;
 | |
| 		return 0;
 | |
| 	}
 | |
| 
 | |
| 	JSONObject jError = new JSONObject();
 | |
| 	jError.SetString("error", "No handler found for requested method.");
 | |
| 	jResponse.Set("error", jError);
 | |
| 	return -1;
 | |
| }
 | |
| 
 | |
| int PublishEvent(int Client, JSONObject Object)
 | |
| {
 | |
| 	if(Client < 0 || Client > MAX_CLIENTS || g_Client_Socket[Client] == null)
 | |
| 		return -1;
 | |
| 
 | |
| 	static char sEvent[4096];
 | |
| 	Object.ToString(sEvent, sizeof(sEvent));
 | |
| 	g_Client_Socket[Client].WriteNull(sEvent);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int GetFreeClientIndex()
 | |
| {
 | |
| 	for(int i = 0; i < MAX_CLIENTS; i++)
 | |
| 	{
 | |
| 		if(!g_Client_Socket[i])
 | |
| 			return i;
 | |
| 	}
 | |
| 	return -1;
 | |
| }
 | |
| 
 | |
| static int ClientFromSocket(AsyncSocket socket)
 | |
| {
 | |
| 	for(int i = 0; i < MAX_CLIENTS; i++)
 | |
| 	{
 | |
| 		if(g_Client_Socket[i] == socket)
 | |
| 			return i;
 | |
| 	}
 | |
| 	return -1;
 | |
| }
 |