diff --git a/src/AgileConfig.Server.Apisite/Startup.cs b/src/AgileConfig.Server.Apisite/Startup.cs index faee737d..0afaf10a 100644 --- a/src/AgileConfig.Server.Apisite/Startup.cs +++ b/src/AgileConfig.Server.Apisite/Startup.cs @@ -1,10 +1,12 @@ using System; using System.IO; using System.Net; +using System.Net.Http; using System.Text; using AgileConfig.Server.Apisite.UIExtension; using AgileConfig.Server.Apisite.Websocket; using AgileConfig.Server.Common; +using AgileConfig.Server.Common.RestClient; using AgileConfig.Server.Data.Freesql; using AgileConfig.Server.OIDC; using AgileConfig.Server.Service; @@ -27,13 +29,23 @@ public Startup(IConfiguration configuration, ILoggerFactory loggerFactory) Configuration = configuration; Global.LoggerFactory = loggerFactory; - TrustSSL(configuration); + SetTrustSSL(configuration); } - private void TrustSSL(IConfiguration configuration) + private static bool IsTrustSSL(IConfiguration configuration) { var alwaysTrustSsl = configuration["alwaysTrustSsl"]; if (!string.IsNullOrEmpty(alwaysTrustSsl) && alwaysTrustSsl.ToLower() == "true") + { + return true; + } + + return false; + } + + private static void SetTrustSSL(IConfiguration configuration) + { + if (IsTrustSSL(configuration)) { ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true; } @@ -47,6 +59,9 @@ public IConfiguration Configuration // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { + services.AddDefaultHttpClient(IsTrustSSL(Configuration)); + services.AddRestClient(); + var jwtService = new JwtService(); services.AddMemoryCache(); services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) @@ -71,6 +86,7 @@ public void ConfigureServices(IServiceCollection services) services.AddBusinessServices(); services.AddHostedService(); services.AddAntiforgery(o => o.SuppressXFrameOptionsHeader = true); + services.AddOIDC(); } @@ -138,5 +154,7 @@ private void AddSwaggerMiddleWare(IApplicationBuilder app) c.SwaggerEndpoint("v1/swagger.json", "My API V1"); }); } + + } } diff --git a/src/AgileConfig.Server.Apisite/StartupExtension.cs b/src/AgileConfig.Server.Apisite/StartupExtension.cs new file mode 100644 index 00000000..80d2ab90 --- /dev/null +++ b/src/AgileConfig.Server.Apisite/StartupExtension.cs @@ -0,0 +1,32 @@ +using AgileConfig.Server.Common; +using Microsoft.Extensions.DependencyInjection; +using System.Net.Http; + +namespace AgileConfig.Server.Apisite +{ + public static class StartupExtension + { + public static void AddDefaultHttpClient(this IServiceCollection services, bool isTrustSSL) + { + services.AddHttpClient(Global.DefaultHttpClientName) + .ConfigurePrimaryHttpMessageHandler(() => { + return NewMessageHandler(isTrustSSL); + }) + ; + } + + static HttpMessageHandler NewMessageHandler(bool alwaysTrustSsl) + { + var handler = new HttpClientHandler(); + if (alwaysTrustSsl) + { + handler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => + { + return true; + }; + } + + return handler; + } + } +} diff --git a/src/AgileConfig.Server.Common/Global.cs b/src/AgileConfig.Server.Common/Global.cs index 275e862d..01ace172 100644 --- a/src/AgileConfig.Server.Common/Global.cs +++ b/src/AgileConfig.Server.Common/Global.cs @@ -24,5 +24,7 @@ public static ILoggerFactory LoggerFactory _loggerFactory = value; } } + + public const string DefaultHttpClientName = "Default"; } } diff --git a/src/AgileConfig.Server.Common/RestClient/DefaultRestClient.cs b/src/AgileConfig.Server.Common/RestClient/DefaultRestClient.cs new file mode 100644 index 00000000..e9830e42 --- /dev/null +++ b/src/AgileConfig.Server.Common/RestClient/DefaultRestClient.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Threading.Tasks; + +namespace AgileConfig.Server.Common.RestClient +{ + public class DefaultRestClient : IRestClient + { + private readonly IHttpClientFactory _httpClientFactory; + + public DefaultRestClient( + IHttpClientFactory httpClientFactory + ) + { + this._httpClientFactory = httpClientFactory; + } + + private HttpClient GetDefaultClient() + { + return _httpClientFactory.CreateClient(Global.DefaultHttpClientName); + } + + public async Task GetAsync(string url, Dictionary headers = null) + { + using var resp = await GetAsync(url, headers); + resp.EnsureSuccessStatusCode(); + + var json = await resp.Content.ReadAsStringAsync(); + return Newtonsoft.Json.JsonConvert.DeserializeObject(json); + } + + public async Task PostAsync(string url, object data, Dictionary headers = null) + { + using var resp = await PostAsync(url, data, headers); + + resp.EnsureSuccessStatusCode(); + + var json = await resp.Content.ReadAsStringAsync(); + return Newtonsoft.Json.JsonConvert.DeserializeObject(json); + } + + public async Task PostAsync(string url, object data, Dictionary headers = null) + { + var httpclient = GetDefaultClient(); + if (headers != null) + { + foreach (var header in headers) + { + httpclient.DefaultRequestHeaders.Add(header.Key, header.Value); + } + } + + var jsondata = ""; + if (data != null) + { + jsondata = Newtonsoft.Json.JsonConvert.SerializeObject(data); + } + var stringContent = new StringContent(jsondata, + new System.Net.Http.Headers.MediaTypeHeaderValue("application/json")); + + var resp = await httpclient.PostAsync(url, stringContent); + + return resp; + } + + public async Task GetAsync(string url, Dictionary headers = null) + { + var httpclient = GetDefaultClient(); + if (headers != null) + { + foreach (var header in headers) + { + httpclient.DefaultRequestHeaders.Add(header.Key, header.Value); + } + } + var resp = await httpclient.GetAsync(url); + + return resp; + } + } +} diff --git a/src/AgileConfig.Server.Common/RestClient/IRestClient.cs b/src/AgileConfig.Server.Common/RestClient/IRestClient.cs new file mode 100644 index 00000000..d379c07e --- /dev/null +++ b/src/AgileConfig.Server.Common/RestClient/IRestClient.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using System.Net.Http; +using System.Threading.Tasks; + +namespace AgileConfig.Server.Common.RestClient +{ + public interface IRestClient + { + Task GetAsync(string url, Dictionary headers = null); + + Task GetAsync(string url, Dictionary headers = null); + + Task PostAsync(string url, object data, Dictionary headers = null); + + Task PostAsync(string url, object data, Dictionary headers = null); + + } +} diff --git a/src/AgileConfig.Server.Common/RestClient/ServiceCollectionEx.cs b/src/AgileConfig.Server.Common/RestClient/ServiceCollectionEx.cs new file mode 100644 index 00000000..e9d0bcd7 --- /dev/null +++ b/src/AgileConfig.Server.Common/RestClient/ServiceCollectionEx.cs @@ -0,0 +1,12 @@ +using Microsoft.Extensions.DependencyInjection; + +namespace AgileConfig.Server.Common.RestClient +{ + public static class ServiceCollectionEx + { + public static void AddRestClient(this IServiceCollection sc) + { + sc.AddScoped(); + } + } +} diff --git a/src/AgileConfig.Server.IService/IRegisterCenterService.cs b/src/AgileConfig.Server.IService/IRegisterCenterService.cs index 74f12a3b..02c07a99 100644 --- a/src/AgileConfig.Server.IService/IRegisterCenterService.cs +++ b/src/AgileConfig.Server.IService/IRegisterCenterService.cs @@ -1,8 +1,4 @@ using AgileConfig.Server.Data.Entity; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Threading.Tasks; namespace AgileConfig.Server.IService diff --git a/src/AgileConfig.Server.OIDC/OidcClient.cs b/src/AgileConfig.Server.OIDC/OidcClient.cs index b13fcab8..73e6d0b9 100644 --- a/src/AgileConfig.Server.OIDC/OidcClient.cs +++ b/src/AgileConfig.Server.OIDC/OidcClient.cs @@ -1,4 +1,5 @@ -using AgileConfig.Server.OIDC.SettingProvider; +using AgileConfig.Server.Common; +using AgileConfig.Server.OIDC.SettingProvider; using AgileConfig.Server.OIDC.TokenEndpointAuthMethods; using Newtonsoft.Json; using System.IdentityModel.Tokens.Jwt; @@ -10,11 +11,13 @@ public class OidcClient : IOidcClient { private readonly IOidcSettingProvider _oidcSettingProvider; private readonly OidcSetting _oidcSetting; + private readonly IHttpClientFactory _httpClientFactory; public OidcSetting OidcSetting => _oidcSetting; - public OidcClient(IOidcSettingProvider oidcSettingProvider) + public OidcClient(IOidcSettingProvider oidcSettingProvider, IHttpClientFactory httpClientFactory) { + _httpClientFactory = httpClientFactory; _oidcSettingProvider = oidcSettingProvider; _oidcSetting = _oidcSettingProvider.GetSetting(); } @@ -35,7 +38,8 @@ public string GetAuthorizeUrl() var authMethod = TokenEndpointAuthMethodFactory.Create(_oidcSetting.TokenEndpointAuthMethod); var httpContent = authMethod.GetAuthHttpContent(code, _oidcSetting); - using var httpclient = new HttpClient(); + + var httpclient = _httpClientFactory.CreateClient(Global.DefaultHttpClientName); if (!string.IsNullOrEmpty(httpContent.BasicAuthorizationString)) { httpclient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", httpContent.BasicAuthorizationString); diff --git a/src/AgileConfig.Server.Service/AgileConfig.Server.Service.csproj b/src/AgileConfig.Server.Service/AgileConfig.Server.Service.csproj index 363061e7..ea67d43a 100644 --- a/src/AgileConfig.Server.Service/AgileConfig.Server.Service.csproj +++ b/src/AgileConfig.Server.Service/AgileConfig.Server.Service.csproj @@ -4,10 +4,6 @@ net8.0 - - - - diff --git a/src/AgileConfig.Server.Service/EventRegisterService/EventRegister.cs b/src/AgileConfig.Server.Service/EventRegisterService/EventRegister.cs index 9a1f3d1b..a99ed8be 100644 --- a/src/AgileConfig.Server.Service/EventRegisterService/EventRegister.cs +++ b/src/AgileConfig.Server.Service/EventRegisterService/EventRegister.cs @@ -1,4 +1,5 @@ -using AgileConfig.Server.IService; +using AgileConfig.Server.Common.RestClient; +using AgileConfig.Server.IService; using Microsoft.Extensions.Logging; namespace AgileConfig.Server.Service.EventRegisterService; @@ -10,12 +11,12 @@ public class EventRegister : IEventRegister private readonly SysLogRegister _sysLogRegister; private readonly ServiceInfoStatusUpdateRegister _serviceInfoStatusUpdateRegister; - public EventRegister(IRemoteServerNodeProxy remoteServerNodeProxy, ILoggerFactory loggerFactory) + public EventRegister(IRemoteServerNodeProxy remoteServerNodeProxy, ILoggerFactory loggerFactory, IRestClient restClient) { _remoteServerNodeProxy = remoteServerNodeProxy; _configStatusUpdateRegister = new ConfigStatusUpdateRegister(_remoteServerNodeProxy); _sysLogRegister = new SysLogRegister(); - _serviceInfoStatusUpdateRegister = new ServiceInfoStatusUpdateRegister(_remoteServerNodeProxy, loggerFactory); + _serviceInfoStatusUpdateRegister = new ServiceInfoStatusUpdateRegister(_remoteServerNodeProxy, loggerFactory, restClient); } public void Register() diff --git a/src/AgileConfig.Server.Service/EventRegisterService/ServiceInfoStatusUpdateRegister.cs b/src/AgileConfig.Server.Service/EventRegisterService/ServiceInfoStatusUpdateRegister.cs index 24b32346..fc001bcf 100644 --- a/src/AgileConfig.Server.Service/EventRegisterService/ServiceInfoStatusUpdateRegister.cs +++ b/src/AgileConfig.Server.Service/EventRegisterService/ServiceInfoStatusUpdateRegister.cs @@ -1,13 +1,14 @@ using System; using System.Linq; using System.Net; +using System.Net.Http; using System.Threading.Tasks; using Agile.Config.Protocol; using AgileConfig.Server.Common; +using AgileConfig.Server.Common.RestClient; using AgileConfig.Server.Data.Entity; using AgileConfig.Server.Data.Freesql; using AgileConfig.Server.IService; -using AgileHttp; using Microsoft.Extensions.Logging; namespace AgileConfig.Server.Service.EventRegisterService; @@ -15,11 +16,17 @@ namespace AgileConfig.Server.Service.EventRegisterService; internal class ServiceInfoStatusUpdateRegister : IEventRegister { private readonly IRemoteServerNodeProxy _remoteServerNodeProxy; + private readonly IRestClient _restClient; private ILogger _logger; - public ServiceInfoStatusUpdateRegister(IRemoteServerNodeProxy remoteServerNodeProxy, ILoggerFactory loggerFactory) + public ServiceInfoStatusUpdateRegister( + IRemoteServerNodeProxy remoteServerNodeProxy, + ILoggerFactory loggerFactory, + IRestClient restClient + ) { _remoteServerNodeProxy = remoteServerNodeProxy; + _restClient = restClient; _logger = loggerFactory.CreateLogger(); } @@ -129,14 +136,11 @@ private async Task SendServiceOfflineMessageAsync(ServiceInfo service) { await FunctionUtil.TRYAsync(async () => { - var resp = await service.AlarmUrl.AsHttp("POST", msg).Config(new RequestOptions() - { - ContentType = "application/json" - }).SendAsync(); - if (resp.Exception != null) - { - throw resp.Exception; - } + var content = new StringContent(""); + content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"); + using var resp = await _restClient.PostAsync(service.AlarmUrl, null); + + resp.EnsureSuccessStatusCode(); return resp.StatusCode == HttpStatusCode.OK; }, 5); diff --git a/src/AgileConfig.Server.Service/RemoteServerNodeProxy.cs b/src/AgileConfig.Server.Service/RemoteServerNodeProxy.cs index ec2130ea..1edb261c 100644 --- a/src/AgileConfig.Server.Service/RemoteServerNodeProxy.cs +++ b/src/AgileConfig.Server.Service/RemoteServerNodeProxy.cs @@ -1,38 +1,20 @@ using Agile.Config.Protocol; using AgileConfig.Server.Common; +using AgileConfig.Server.Common.RestClient; using AgileConfig.Server.Data.Entity; using AgileConfig.Server.Data.Freesql; using AgileConfig.Server.IService; -using AgileHttp; -using AgileHttp.serialize; using Microsoft.Extensions.Logging; -using Newtonsoft.Json; -using Newtonsoft.Json.Serialization; using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Security.Policy; using System.Threading.Tasks; namespace AgileConfig.Server.Service { public class RemoteServerNodeProxy : IRemoteServerNodeProxy { - internal class SerializeProvider : ISerializeProvider - { - public T Deserialize(string content) - { - return JsonConvert.DeserializeObject(content, new JsonSerializerSettings - { - ContractResolver = new CamelCasePropertyNamesContractResolver() - }); - } - - public string Serialize(object obj) - { - return JsonConvert.SerializeObject(obj); - } - } - private IServerNodeService GetServerNodeService() { return new ServerNodeService(new FreeSqlContext(FreeSQL.Instance)); @@ -47,33 +29,25 @@ private ISysLogService GetSysLogService() private static ConcurrentDictionary _serverNodeClientReports = new ConcurrentDictionary(); + private readonly IRestClient _restClient; - public RemoteServerNodeProxy(ILoggerFactory loggerFactory) + public RemoteServerNodeProxy(ILoggerFactory loggerFactory, IRestClient restClient) { _logger = loggerFactory.CreateLogger(); + _restClient = restClient; } public async Task AllClientsDoActionAsync(string address, WebsocketAction action) { var result = await FunctionUtil.TRYAsync(async () => { - using (var resp = await (address + "/RemoteOP/AllClientsDoAction") - .AsHttp("POST", action) - .Config(new RequestOptions { ContentType = "application/json" }) - .SendAsync()) + dynamic result = await _restClient.PostAsync(address + "/RemoteOP/AllClientsDoAction", action); + if ((bool)result.success) { - if (resp.StatusCode == System.Net.HttpStatusCode.OK) - { - var result = resp.Deserialize(); - - if ((bool)result.success) - { - return true; - } - } - - return false; + return true; } + + return false; }, 5); using (var service = GetSysLogService()) @@ -102,23 +76,14 @@ public async Task AppClientsDoActionAsync(string address, string appId, st { var result = await FunctionUtil.TRYAsync(async () => { - using (var resp = await (address + "/RemoteOP/AppClientsDoAction".AppendQueryString("appId", appId).AppendQueryString("env", env)) - .AsHttp("POST", action) - .Config(new RequestOptions { ContentType = "application/json" }) - .SendAsync()) + var url = $"{address}/RemoteOP/AppClientsDoAction?appId={Uri.EscapeDataString(appId)}&env={env}"; + dynamic result = await _restClient.PostAsync(url, action); + if ((bool)result.success) { - if (resp.StatusCode == System.Net.HttpStatusCode.OK) - { - var result = resp.Deserialize(); - - if ((bool)result.success) - { - return true; - } - } - - return false; + return true; } + + return false; }, 5); using (var service = GetSysLogService()) @@ -148,36 +113,28 @@ public async Task OneClientDoActionAsync(string address, string clientId, { var result = await FunctionUtil.TRYAsync(async () => { - using (var resp = await (address + "/RemoteOP/OneClientDoAction?clientId=" + clientId) - .AsHttp("POST", action) - .Config(new RequestOptions { ContentType = "application/json" }) - .SendAsync()) + var url = $"{address}/RemoteOP/OneClientDoAction?clientId={clientId}"; + dynamic result = await _restClient.PostAsync(url, action); + + if ((bool)result.success) { - if (resp.StatusCode == System.Net.HttpStatusCode.OK) + if (action.Action == ActionConst.Offline) { - var result = resp.Deserialize(); - - if ((bool)result.success) + if (_serverNodeClientReports.ContainsKey(address)) { - if (action.Action == ActionConst.Offline) + if (_serverNodeClientReports[address].Infos != null) { - if (_serverNodeClientReports.ContainsKey(address)) - { - if (_serverNodeClientReports[address].Infos != null) - { - var report = _serverNodeClientReports[address]; - report.Infos.RemoveAll(c => c.Id == clientId); - report.ClientCount = report.Infos.Count; - } - } + var report = _serverNodeClientReports[address]; + report.Infos.RemoveAll(c => c.Id == clientId); + report.ClientCount = report.Infos.Count; } - - return true; } } - return false; + return true; } + + return false; }, 5); using (var service = GetSysLogService()) @@ -215,25 +172,20 @@ public async Task GetClientsReportAsync(string address) try { - using (var resp = await (address + "/report/Clients").AsHttp() - .Config(new RequestOptions(new SerializeProvider())).SendAsync()) - { - if (resp.StatusCode == System.Net.HttpStatusCode.OK) - { - var clients = resp.Deserialize(); - if (clients != null) - { - clients.Infos?.ForEach(i => { i.Address = address; }); - return clients; - } - } + var url = address + "/report/Clients"; - return new ClientInfos() - { - ClientCount = 0, - Infos = new List() - }; + var clients = await _restClient.GetAsync(url); + if (clients != null) + { + clients.Infos?.ForEach(i => { i.Address = address; }); + return clients; } + + return new ClientInfos() + { + ClientCount = 0, + Infos = new List() + }; } catch (Exception ex) { @@ -253,8 +205,11 @@ public async Task TestEchoAsync(string address) var node = await service.GetAsync(address); try { - using var resp = await (node.Address + "/home/echo").AsHttp().SendAsync(); - if (resp.StatusCode == System.Net.HttpStatusCode.OK && (await resp.GetResponseContentAsync()) == "ok") + var url = node.Address + "/home/echo"; + + using var resp = await _restClient.GetAsync(url); + + if (resp.StatusCode == System.Net.HttpStatusCode.OK && (await resp.Content.ReadAsStringAsync()) == "ok") { node.LastEchoTime = DateTime.Now; node.Status = NodeStatus.Online; @@ -310,8 +265,9 @@ public Task TestEchoAsync() public async Task ClearConfigServiceCache(string address) { try - { - await (address + "/RemoteOP/ClearConfigServiceCache").AsHttp("POST").SendAsync(); + { + var url = (address + "/RemoteOP/ClearConfigServiceCache"); + using var resp = await _restClient.PostAsync(url, null); } catch (Exception e) { @@ -322,8 +278,11 @@ public async Task ClearConfigServiceCache(string address) public async Task ClearServiceInfoCache(string address) { try - { - await (address + "/RemoteOP/ClearServiceInfoCache").AsHttp("POST").SendAsync(); + { + var url = (address + "/RemoteOP/ClearServiceInfoCache"); + + await _restClient.PostAsync(url, null); + } catch (Exception e) { diff --git a/src/AgileConfig.Server.Service/ServiceCollectionExt.cs b/src/AgileConfig.Server.Service/ServiceCollectionExt.cs index cd3cf887..66490646 100644 --- a/src/AgileConfig.Server.Service/ServiceCollectionExt.cs +++ b/src/AgileConfig.Server.Service/ServiceCollectionExt.cs @@ -1,4 +1,5 @@ -using AgileConfig.Server.IService; +using AgileConfig.Server.Common.RestClient; +using AgileConfig.Server.IService; using AgileConfig.Server.Service.EventRegisterService; using Microsoft.Extensions.DependencyInjection; @@ -10,11 +11,11 @@ public static void AddBusinessServices(this IServiceCollection sc) { sc.AddSingleton(); - sc.AddSingleton(); - sc.AddSingleton(); - sc.AddSingleton(); - sc.AddSingleton(); - + sc.AddScoped(); + sc.AddScoped(); + sc.AddScoped(); + sc.AddScoped(); + sc.AddScoped(); sc.AddScoped(); sc.AddScoped(); diff --git a/src/AgileConfig.Server.Service/ServiceHealthCheckService.cs b/src/AgileConfig.Server.Service/ServiceHealthCheckService.cs index e7ed3f20..0474d275 100644 --- a/src/AgileConfig.Server.Service/ServiceHealthCheckService.cs +++ b/src/AgileConfig.Server.Service/ServiceHealthCheckService.cs @@ -1,10 +1,10 @@ using System; using System.Threading.Tasks; using AgileConfig.Server.Common; +using AgileConfig.Server.Common.RestClient; using AgileConfig.Server.Data.Entity; using AgileConfig.Server.Data.Freesql; using AgileConfig.Server.IService; -using AgileHttp; using Microsoft.Extensions.Logging; namespace AgileConfig.Server.Service; @@ -12,14 +12,18 @@ namespace AgileConfig.Server.Service; public class ServiceHealthCheckService : IServiceHealthCheckService { private readonly ILogger _logger; + private readonly IRestClient _restClient; private readonly IServiceInfoService _serviceInfoService; public ServiceHealthCheckService( IServiceInfoService serviceInfoService, - ILogger logger) + ILogger logger, + IRestClient restClient + ) { _serviceInfoService = serviceInfoService; _logger = logger; + _restClient = restClient; } private int _checkInterval; @@ -189,20 +193,11 @@ private async Task CheckAService(ServiceInfo service) { try { - using var resp = await service.CheckUrl - .AsHttp() - .SendAsync(); - if (resp.Exception != null) - { - throw resp.Exception; - } + using var resp = await _restClient.GetAsync(service.CheckUrl); var result = false; - if (resp.StatusCode.HasValue) - { - int istatus = ((int)resp.StatusCode - 200); - result = istatus >= 0 && istatus < 100; // 200 段都认为是正常的 - } + int istatus = ((int)resp.StatusCode - 200); + result = istatus >= 0 && istatus < 100; // 200 段都认为是正常的 _logger.LogInformation("check service health {0} {1} {2} result:{3}", service.CheckUrl, service.ServiceId, service.ServiceName, result ? "up" : "down");