Thread Rating:
  • 1 Vote(s) - 5 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Call C# functions from anywhere using URL (HTTP server)
#1
Code:
Copy      Help
// script "HTTP server.cs"
/// Simple HTTP server that executes your functions defined in this script.
/// Add any functions to the Functions class, and run this script. Several examples are included.
/// Then any script or program on this computer or any device in the world can use URL like "http://ThisComputer:4455/FunctionName?a=100" to execute these functions.
/// More info in <see cref="HttpServerSession.Listen"/> documentation (click Listen and press F1).

using System.Net;
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Nodes;

HttpServerSession.Listen<HttpSession>();

class HttpSession : HttpServerSession {
    protected override void MessageReceived(HSMessage m, HSResponse r) {
        //Auth((u, p) => p == "password278351"); //enable this if want to protect this HTTP server with a password
        
        //print.it(m.Method, m.TargetPath, m.UrlParameters, m.Text, m.Headers);

        
        Dictionary<string, string> d = null;
        if (m.Method == "GET") {
            d = m.UrlParameters;
        }
else if (m.Method == "POST") {
            d = m.Urlencoded;
        }
else {
            r.Status = HttpStatusCode.BadRequest;
            return;
        }

        
        if (m.TargetPath.Starts("/") && typeof(Functions).GetMethod(m.TargetPath[1..]) is { } mi) {
            var p = mi.GetParameters();
            var a = new object[p.Length];
            bool contentUsed = false;
            try {
                for (int i = 0; i < p.Length; i++) {
                    var v = p[i];
                    var name = v.Name;
                    var t = v.ParameterType;
                    if (t == typeof(HSMessage)) a[i] = m;
                    else if (t == typeof(HSResponse)) a[i] = r;
                    else if (t == typeof(HSContentPart)) a[i] = m.Multipart?.GetValueOrDefault(name);
                    else {
                        string s = null;
                        bool isArg = true == d?.TryGetValue(name, out s);
                        if (!isArg) if (true == m.Multipart?.TryGetValue(name, out var u)) { isArg = true; s = u.Text ?? u.Content?.ToStringUTF8(); }
                        if (isArg) {
                            if (s != null) {
                                if (t == typeof(string)) a[i] = s;
                                else if (s[0] is '{' or '[') a[i] = _FromJson(s);
                                else a[i] = Convert.ChangeType(s, t);
                            }
                        }
else if (!contentUsed && !v.IsOptional && m.Text is string text) {
                            contentUsed = true;
                            if (t == typeof(string)) a[i] = text;
                            else if (m.ContentType?.MediaType == "application/json" || (text.Length >= 2 && text[0] is '{' or '[')) a[i] = _FromJson(text);
                        }

                        
                        object _FromJson(string s) {
                            if (t.IsAssignableTo(typeof(JsonNode))) return JsonNode.Parse(s);
                            return JsonSerializer.Deserialize(s, t);
                        }
                    }

                    a[i] ??= v.IsOptional ? v.DefaultValue : throw new Exception($"Parameter {name} not optional");
                }
            }

            catch (Exception e1) {
                r.Status = HttpStatusCode.BadRequest;
                r.SetContentText(e1.Message);
                return;
            }

            
            var g = mi.Invoke(null, a);
            
            if (g != null) {
                if (mi.ReturnTypeCustomAttributes.IsDefined(typeof(JSONAttribute), false) && g.GetType() is {  } t1) {
                    r.Content = JsonSerializer.SerializeToUtf8Bytes(g, t1, s_defaultSerializerOptions.Value);
                    r.Headers["Content-Type"] = "application/json; charset=utf-8";
                }
else {
                    r.SetContentText(g.ToString());
                }
            }
        }
else {
            r.Status = HttpStatusCode.NotFound;
        }
    }

    
    static readonly Lazy<JsonSerializerOptions> s_defaultSerializerOptions = new(() => new(JsonSerializerDefaults.Web));
}


/// <summary>
///
If a function has this attribute, the returned object will be converted to JSON string and returned as response content + corresponding <c>Content-Type</c> header.
/// Example: <c>[return: JSON] object ReturnJsonExample() { ... return objectOfAnyType; }</c>.
/// </summary>
[AttributeUsage(AttributeTargets.ReturnValue)]
sealed class JSONAttribute : Attribute { }

/// <summary>
///
Add your functions to this class. To call public functions, clients use HTTP GET or POST request with URL like <c>"http://ThisComputer:4455/FunctionName"</c>.
/// Clients can specify parameter names and values in URL like <c><![CDATA["http://ThisComputer:4455/FunctionName?a=text&b=100"]]></c>. Or POST urlencoded or multipart parameters or any data.
/// Clients must specify parameter names. The order does not matter. Can omit optional parameters.
/// Your functions can have parameters of these types:
/// <br/>• string.
/// <br/>• Types that <b>Convert.ChangeType</b> can convert from string (int, bool, etc).
/// <br/><b>JsonNode</b>, <b>JsonObject</b>, <b>JsonArray</b> and types that <b>JsonSerializer.Deserialize</b> can deserialize. The argument value must be JSON.
/// <br/><b>HSContentPart</b> (if want to access posted binary data etc).
/// <br/><b>HSMessage</b>, <b>HSResponse</b> (if want to access everything).
/// Also clients can pass a simple or JSON string in HTTP message content rather than in URL etc; the server assigns the string or JSON-deserialized object to the first unspecified non-optional parameter of type string or <b>JsonNode</b>, <b>JsonObject</b>, <b>JsonArray</b> or a type that <b>JsonSerializer.Deserialize</b> can deserialize.
/// Your functions can optionally return a non-null value of any type as the response text.
/// Functions that have overloads are not supported.
/// Functions are executed in new thread for each new connection.
/// </summary>
static partial class Functions {
    #region examples
    
    //client example: internet.http.Get("http://localhost:4455/Simple");
    //client example: internet.http.Get("http://localhost:4455/Simple", auth: ":password278351");

    public static void Simple() {
        print.it("Simple");
    }

    
    //client example: string s = internet.http.Get("http://localhost:4455/Returns").Text(); print.it(s);
    public static string Returns() {
        return "RESPONSE";
    }

    
    //client example: internet.http.Get("http://localhost:4455/Parameters?a=aa&c=10&d=true");
    //client example: internet.http.Post("http://localhost:4455/Parameters", internet.formContent(("a", "aa"), ("c", 10)));

    public static void Parameters(string a, string b = null, int c = 0, bool d = false) {
        print.it(a, b, c, d);
    }

    
    //client example: int r = internet.http.Get("http://localhost:4455/Add?x=4&y=7").Text().ToInt(); print.it(r);
    public static int Add(int x, int y) => x + y;
    
    //client example: var p = new POINT(3, 4); var p2 = internet.http.Post("http://localhost:4455/Json", internet.jsonContent(p)).Json<POINT>(); print.it(p2);
    public static void Json(HSMessage m, HSResponse r) {
        POINT p = m.Json<POINT>();
        print.it(p);
        var p2 = new POINT(5, 6);
        r.SetContentJson(p2); //return JSON
    }
    
    //client example: internet.http.Post("http://localhost:4455/JsonSimple", internet.jsonContent(new R1(5, "text"))); record R1(int i, string s);
    //client example with anonymous type: internet.http.Post("http://localhost:4455/JsonSimple", internet.jsonContent(new { i = 5, s = "text" }));

    public static void JsonSimple(R1 x) {
        print.it(x);
    }

    public record R1(int i, string s);
    
    //client example: string s = internet.http.Get("http://localhost:4455/ReturnJsonSimple").Text(); print.it(s);
    //client example: var v = internet.http.Get("http://localhost:4455/ReturnJsonSimple").Json<R1>(); print.it(v); record R1(int i, string s);

    [return: JSON]
    public static R1 ReturnJsonSimple() => new(4, "example"); //use a defined type
    //public static object ReturnJsonSimple() => new { s = "S", i = 5, b = true, a = new object[] { "a0", "a1" } }; //use anonymous type

    
    #endregion
}

Changes
2023-06-22:
- Returns error descriptions in response body, not in status reason phrase.
- Updated client functions in other posts below, to match the above change.
2024-05-06:
- Simpler to use JSON. See examples near the bottom.
2024-05-09:
- Simpler to return JSON. See examples near the bottom.


Messages In This Thread
Call C# functions from anywhere using URL (HTTP server) - by Gintaras - 06-05-2023, 03:58 PM

Forum Jump:


Users browsing this thread: 1 Guest(s)