From 6ca21307f1811c70780194d8d3b3ca78114ff796 Mon Sep 17 00:00:00 2001 From: yangziy Date: Mon, 17 Sep 2018 22:57:23 +0800 Subject: [PATCH] Rewrite server-relevant API. Update document. --- HTTPAPI.md | 134 +++++++++++++++++++++------- ShadowsocksX-NG/HTTPUserProxy.swift | 72 +++++++++++++-- ShadowsocksX-NG/ServerProfile.swift | 25 +++--- 3 files changed, 181 insertions(+), 50 deletions(-) diff --git a/HTTPAPI.md b/HTTPAPI.md index 0830580a..9758b975 100644 --- a/HTTPAPI.md +++ b/HTTPAPI.md @@ -3,13 +3,12 @@ * Check current status (on/off) - Toggle the client - - Get server list - -- Switch server - +- Get current server +- Select server +- Add new / modify existing server +- Delete server - Get current mode - - Switch mode # Specification @@ -26,60 +25,124 @@ URL: http://localhost:9528/ } ``` -- #### Toggle the client `POST /toggle` +- #### Toggle the client `POST /status` ###### Sample Return ``` { "status": 1 - // 1 for toggle succeed, 0 for fail } ``` -- #### Get server list `GET /servers` +`1` for success, `0` for failure. + +- #### Get server list `GET /server/list` ###### Sample Return ``` [ - { - "active": 1, - "id": "93C547E0-49C9-1234-9CAD-EE8D5C4A1A8F", - "remark": "us1", - // remark: as in Server Preferences Panel of the app. - }, - { - "active" : 0, - "id" : "71552DCD-B298-495E-904E-82DA4B07AEF8", - "remark" : "hk2" - }, - { - "active" : 0, - "id" : "E8879F3D-95AE-4714-BC04-9B271C2BC52D", - "remark" : "jp1" - },... + { + "Id" : "93C127E0-49C9-4332-9CAD-EE6B9A3D1A8F", + "Method" : "chacha20-ietf-poly1305", + "Password" : "password", + "Plugin" : "", + "PluginOptions" : "", + "Remark" : "jp1", + "ServerHost" : "jp1-sta40.somehost.com", + "ServerPort" : 49234 + }, + { + "Id" : "71552DCD-B298-4591-B59A-82DA4B07AEF8", + "Method" : "chacha20-ietf-poly1305", + "Password" : "password", + "Plugin" : "", + "PluginOptions" : "", + "Remark" : "us1", + "ServerHost" : "us1-sta40.somehost.com", + "ServerPort" : 49234 + },... ] ``` -- #### Switch server `POST /servers` +- #### Get current server `GET /server/current` + +###### Sample Return + +``` +{ + "Id" : "93C127E0-49C9-4332-9CAD-EE6B9A3D1A8F" +} +``` + +- #### Select server `POST /server/current` ###### Argument -| Name | Description | Sample | -| ---- | ----------------------------- | -------------------------------------- | -| id | As returned in `GET /servers` | "E8879F3D-95AE-4714-BC04-9B271C2BC52D" | +| Name | Description | Sample | +| ---- | --------------------------------- | -------------------------------------- | +| Id | As returned in `GET /server/list` | "71552DCD-B298-4591-B59A-82DA4B07AEF8" | ###### Sample Return ``` { "status": 1 - // 1 for succeed, 0 for fail } ``` -If the `id` is invalid or fail to match any `id` in config, "status" = 0. +If the `Id` is invalid or fail to match any id in config, `"status": 0`. + +- #### Add Server / Modify Existing Server `POST /server ` + +###### Argument + +| Name | Sample | +| ------------- | ---------------------- | +| ServerPort | 49234 | +| ServerHost | jp1-sta40.somehost.com | +| Remark | jp1 | +| PluginOptions | | +| Plugin | | +| Password | Password | +| Method | chacha20-ietf-poly1305 | + +To indicate modification, pass `Id` in addition. + +| Name | Description | Sample | +| ---- | --------------------------------- | -------------------------------------- | +| Id | As returned in `GET /server/list` | "71552DCD-B298-4591-B59A-82DA4B07AEF8" | + +For meaning of the arguments, refer to `GET /server/list` and the Server Perferences Panel of the app. + +###### Sample Return + +``` +{ + "status": 1 +} +``` + +- #### Delete Server `DELETE /server` + +###### Argument + +| Name | Description | Sample | +| ---- | --------------------------------- | -------------------------------------- | +| Id | As returned in `GET /server/list` | "71552DCD-B298-4591-B59A-82DA4B07AEF8" | + +###### Sample Return + +``` +{ + "status": 1 +} +``` + +If `Id` == id of current server, operation will no effect, `"status":0`. + +If `Id` not match, `"status":0`. - #### Get current mode `GET /mode` @@ -95,6 +158,12 @@ If the `id` is invalid or fail to match any `id` in config, "status" = 0. - #### Switch mode `POST /mode` +###### Argument + +| Name | Description | Sample | +| ---- | -------------------------- | -------- | +| mode | As returned in `GET /mode` | "global" | + ###### Sample Return ``` @@ -104,4 +173,7 @@ If the `id` is invalid or fail to match any `id` in config, "status" = 0. } ``` -If the `mode`∉ {"auto", "global", "manual"}, "status" = 0. \ No newline at end of file +--- + +All json names are case sensitive. Be careful. + diff --git a/ShadowsocksX-NG/HTTPUserProxy.swift b/ShadowsocksX-NG/HTTPUserProxy.swift index 5945a995..d477ae6b 100644 --- a/ShadowsocksX-NG/HTTPUserProxy.swift +++ b/ShadowsocksX-NG/HTTPUserProxy.swift @@ -35,36 +35,92 @@ class HTTPUserProxy{ return GCDWebServerDataResponse(jsonObject: ["enable":isOn], contentType: "json") }) - apiserver.addHandler(forMethod: "POST", path: "/toggle", request: GCDWebServerRequest.self, processBlock: {request in + apiserver.addHandler(forMethod: "POST", path: "/status", request: GCDWebServerURLEncodedFormRequest.self, processBlock: {request in self.appdeleget.doToggleRunning(showToast: false) return GCDWebServerDataResponse(jsonObject: ["status":1], contentType: "json") }) - apiserver.addHandler(forMethod: "GET", path: "/servers", request: GCDWebServerRequest.self, processBlock: {request in + apiserver.addHandler(forMethod: "GET", path: "/server/list", request: GCDWebServerRequest.self, processBlock: {request in var data = [[String:Any]]() for each in self.SerMgr.profiles{ - data.append(["id":each.uuid,"remark":each.remark, - "active":self.SerMgr.activeProfileId == each.uuid ? 1 : 0]) + data.append(each.toDictionary()) } return GCDWebServerDataResponse(jsonObject: data, contentType: "json") }) - apiserver.addHandler(forMethod: "POST", path: "/servers", request: GCDWebServerURLEncodedFormRequest.self, processBlock: {request in + apiserver.addHandler(forMethod: "GET", path: "/server/current", request: GCDWebServerRequest.self, processBlock: {request in - let uuid = ((request as! GCDWebServerURLEncodedFormRequest).arguments["id"])as? String + return GCDWebServerDataResponse(jsonObject: ["Id":self.SerMgr.activeProfileId], contentType: "json") + }) + + apiserver.addHandler(forMethod: "POST", path: "/server/current", request: GCDWebServerURLEncodedFormRequest.self, processBlock: {request in + + let uuid = ((request as! GCDWebServerURLEncodedFormRequest).arguments["Id"])as? String for each in self.SerMgr.profiles{ if (each.uuid == uuid) { self.appdeleget.changeServer(uuid: uuid!) return GCDWebServerDataResponse(jsonObject: ["status":1], contentType: "json") - + + } + } + return GCDWebServerDataResponse(jsonObject: ["status":0], contentType: "json") + }) + + apiserver.addHandler(forMethod: "POST", path: "/server", request: GCDWebServerURLEncodedFormRequest.self, processBlock: {request in + + var data = ((request as! GCDWebServerURLEncodedFormRequest).arguments) as! [String: Any] + data["ServerPort"] = Double(data["ServerPort"] as! String) + let id = data["Id"] as? String + if (id != nil) { + for each in self.SerMgr.profiles{ + if (each.uuid == id) { + ServerProfile.copy(fromDict: data, toProfile: each) + if (each.isValid()) { + self.SerMgr.save() + self.appdeleget.updateServersMenu() + return GCDWebServerDataResponse(jsonObject: ["status":1], contentType: "json") + } + } } } + else { + let profile = ServerProfile.fromDictionary(data) + if (profile.isValid()) { + self.SerMgr.profiles.append(profile) + self.SerMgr.save() + self.appdeleget.updateServersMenu() + return GCDWebServerDataResponse(jsonObject: ["status":1], contentType: "json") + } + } + return GCDWebServerDataResponse(jsonObject: ["status":0], contentType: "json") }) + apiserver.addHandler(forMethod: "DELETE", path: "/server", request: GCDWebServerRequest.self + , processBlock: {request in + + let uuid = (request.query?["Id"])as! String + + if (uuid == self.SerMgr.activeProfileId) { + return GCDWebServerDataResponse(jsonObject: ["status":0], contentType: "json") + } + + for i in 0.. ServerProfile { + static func copy(fromDict:[String:Any?], toProfile:ServerProfile) { let cp = { (profile: ServerProfile) in - profile.serverHost = data["ServerHost"] as! String - profile.serverPort = (data["ServerPort"] as! NSNumber).uint16Value - profile.method = data["Method"] as! String - profile.password = data["Password"] as! String - if let remark = data["Remark"] { + profile.serverHost = fromDict["ServerHost"] as! String + profile.serverPort = (fromDict["ServerPort"] as! NSNumber).uint16Value + profile.method = fromDict["Method"] as! String + profile.password = fromDict["Password"] as! String + if let remark = fromDict["Remark"] { profile.remark = remark as! String } - if let plugin = data["Plugin"] as? String { + if let plugin = fromDict["Plugin"] as? String { profile.plugin = plugin } - if let pluginOptions = data["PluginOptions"] as? String { + if let pluginOptions = fromDict["PluginOptions"] as? String { profile.pluginOptions = pluginOptions } } - + cp(toProfile) + } + + static func fromDictionary(_ data:[String:Any?]) -> ServerProfile { if let id = data["Id"] as? String { let profile = ServerProfile(uuid: id) - cp(profile) + copy(fromDict: data, toProfile: profile) return profile } else { let profile = ServerProfile() - cp(profile) + copy(fromDict: data, toProfile: profile) return profile } }