Microsoft MVP성태의 닷넷 이야기
.NET Framework: 791. C# - ElasticSearch를 위한 Client 라이브러리 제작 [링크 복사], [링크+제목 복사]
조회: 1693
글쓴 사람
홈페이지
첨부 파일

C# - ElasticSearch를 위한 Client 라이브러리 제작

사실 Restful API 기반은 지난 글에서도 설명했지만,

C# - JIRA REST API 사용 정리
; https://www.sysnet.pe.kr/2/0/11566

json2csharp 사이트의 도움으로,

json2csharp
; http://json2csharp.com/

쉽게 strong-type의 엔티티 클래스로 구현을 매끄럽게 할 수 있습니다. 문제는, ElasticSearch가 그다지 잘 정립된 JSON 클래스를 사용하지 않는다는 점입니다. 예를 들어, 지난 글에서 실습한 my_blog 인덱스에 대해 GET을 요청하면 다음과 같은 응답이 옵니다.

curl -XGET "http://localhost:9200/my_blog

{
  "my_blog" : {
    "aliases" : { },
    "mappings" : {
      "articles" : {
        "properties" : {
          "contents" : {
            "type" : "text",
            "analyzer" : "blogtext_analyzer"
          },
          "name" : {
            "type" : "text",
            "fields" : {
              "keyword" : {
                "type" : "keyword",
                "ignore_above" : 256
              }
            }
          },
          "registered" : {
            "type" : "date"
          },
          "wid" : {
            "type" : "integer"
          },
          "writer" : {
            "type" : "text",
            "index" : false
          }
        }
      }
    },
    "settings" : {
      ...[생략]...
    }
  }
}

저걸 json 역직렬화하려면 다음과 같이 도메인에 특화된 용어가 클래스의 필드로 와야 합니다.

public class IndexGetResult
{
    public MyOrg my_org { get; set; }
}

달리 말하면, XML을 정의할 때 item과 price 값을 다음과 같이 구성한 것이 아니고,

<item name="monitor" value="500" />
<item name="computer" value="600" />

다음과 같은 식으로 구성한 식이 되는 것입니다.

<monitor value="500" />
<computer value="600" />

저 2개에 대해 XML Schema를 정의한다고 했을 때 첫 번째 사례는 item 노드만 정의하면 되지만 두 번째 사례는 monitor와 computer를 정의해야 합니다. 게다가 다른 제품이 추가되면 두 번째를 위한 Schema는 다시 변경해야 하는... 전혀 유연성이 없는 구조인 것입니다.




그렇다고는 해도, 어쨌든 잘 만들어진 라이브러리를 사용하면 됩니다. ^^ 닷넷의 경우도 검색해 보면 다음과 같이 elasticsearch-net 라이브러리가 있어서,

elastic/elasticsearch-net 
; https://github.com/elastic/elasticsearch-net

Install-Package Elasticsearch.Net

Fluent 방식으로 구성되어 있으니,

var response = client.Search<Tweet>(s => s
    .From(0)
    .Size(10)
    .Query(q => q
        .Term(t => t.User, "kimchy") || q
        .Match(mq => mq.Field(f => f.User).Query("nest"))
    )
);

잘 사용하시면 됩니다. ^^




물론, 직접 만드는 방법도 있습니다. 주의할 것은, 오픈 소스들의 특성상 변화가 무쌍하기 때문에 그에 따른 유지 보수는 책임질 각오를 해야 합니다. 그래도 만들고 싶다면 네트워크 호출 부분과 기본적인 Elasticsearch와의 통신 부분을 가볍게 나누는 것이 좋습니다.

using System.Net;
using System.Net.Http;
using System.Threading.Tasks;

namespace ElasticSearchLib
{
    public class LLClient
    {
        HttpClient _httpClient;
        CookieContainer _cookies;

        public LLClient()
        {
            _cookies = new CookieContainer();

            HttpClientHandler handler = new HttpClientHandler();
            handler.CookieContainer = _cookies;

            _httpClient = new HttpClient(handler);
        }

        public async Task<string> GetAsync(string query)
        {
            HttpResponseMessage hrm = await _httpClient.GetAsync(query);
            return await hrm.Content.ReadAsStringAsync();
        }

        public async Task<string> DeleteAsync(string query)
        {
            HttpResponseMessage hrm = await _httpClient.DeleteAsync(query);
            return await hrm.Content.ReadAsStringAsync();
        }

        public async Task<string> PutAsync(string query, string putData)
        {
            StringContent putContent = new StringContent(putData);
            putContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
            
            HttpResponseMessage hrm = await _httpClient.PutAsync(query, putContent);
            return await hrm.Content.ReadAsStringAsync();
        }

        internal async Task<string> PostAsync(string query, string putData)
        {
            StringContent putContent = new StringContent(putData);
            putContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");

            HttpResponseMessage hrm = await _httpClient.PostAsync(query, putContent);
            return await hrm.Content.ReadAsStringAsync();
        }
    }
}

그다음, 이것을 이용해 Elasticsearch에 대한 것을 만들어 나가면 됩니다.

using Newtonsoft.Json.Linq;
using System;
using System.IO;
using System.Threading.Tasks;

namespace ElasticSearchLib
{
    public class ElasticSearchClient
    {
        string _server;
        short _port;

        string _node;
        public string Node
        {
            get { return _node; }
        }

        LLClient _client;
        public LLClient NetworkClient
        {
            get { return _client; }
        }

        ErrorInfo _lastErrorInfo;
        public ErrorInfo LastErrorInfo
        {
            get { return _lastErrorInfo; }
        }

        protected bool DetectErrorInfo(dynamic resultObject)
        {
            if (resultObject.error != null)
            {
                _lastErrorInfo = resultObject.ToObject<ErrorInfo>();
                return true;
            }

            return false;
        }

        public ElasticSearchClient(string server, short port)
        {
            _server = server;
            _port = port;

            _node = string.Format("http://{0}:{1}", _server, _port);
            _client = new LLClient();
        }

        public async Task<bool> ConnectAsync(string expectedVersion)
        {
            string responseText = await _client.GetAsync(_node);
            if (string.IsNullOrEmpty(responseText) == true)
            {
                return false;
            }

            ServerInfo result = Newtonsoft.Json.JsonConvert.DeserializeObject<ServerInfo>(responseText);

            if (expectedVersion == null)
            {
                return true;
            }

            System.Version target = new System.Version(result.version.number);
            System.Version expected = new System.Version(expectedVersion);

            return target.Major == expected.Major
                && target.Minor == expected.Minor
                && target.Build == expected.Build
                && (target.Revision == -1 || target.Revision == expected.Revision);
        }

        protected string GetResurceText(Type baseType, string resourceName)
        {
            string fqdn = string.Format("{0}.Entity.{1}", baseType.Namespace, resourceName);
            using (Stream stream = baseType.Assembly.GetManifestResourceStream(fqdn))
            using (StreamReader reader = new StreamReader(stream))
            {
                return reader.ReadToEnd();   
            }
        }

        string GetResurceText(string resourceName)
        {
            return GetResurceText(typeof(ElasticSearchClient), resourceName);
        }

        protected async Task<bool> CreateIndexAsync(string indexName)
        {
            string indexUrl = string.Format("{0}/{1}", _node, indexName);

            string putData = GetResurceText("CreateIndex.json");
            string responseText = await _client.PutAsync(indexUrl, putData);

            if (string.IsNullOrEmpty(responseText) == true)
            {
                return false;
            }

            dynamic indexResult = Newtonsoft.Json.JsonConvert.DeserializeObject(responseText);
            JObject jObject = indexResult as JObject;

            if (DetectErrorInfo(indexResult) == true)
            {
                return false;
            }

            RequestResult result = jObject.ToObject<RequestResult>();
            return result.acknowledged;
        }

        public async Task<bool> RemoveIndexAsync(string indexName)
        {
            string indexUrl = string.Format("{0}/{1}", _node, indexName);
            string responseText = await _client.DeleteAsync(indexUrl);

            if (string.IsNullOrEmpty(responseText) == true)
            {
                return false;
            }

            dynamic indexResult = Newtonsoft.Json.JsonConvert.DeserializeObject(responseText);
            JObject jObject = indexResult as JObject;

            if (DetectErrorInfo(indexResult) == true)
            {
                return false;
            }

            RequestResult result = jObject.ToObject<RequestResult>();
            return result.acknowledged;
        }

        public async Task<bool> CreateTypeAsync(string indexName, string typeName, string putData)
        {
            string indexUrl = string.Format("{0}/{1}/{2}/_mapping", Node, indexName, typeName);
            string responseText = await NetworkClient.PutAsync(indexUrl, putData);
            if (string.IsNullOrEmpty(responseText) == true)
            {
                return false;
            }

            dynamic typeResult = Newtonsoft.Json.JsonConvert.DeserializeObject(responseText);
            JObject jObject = typeResult as JObject;

            if (DetectErrorInfo(typeResult) == true)
            {
                return false;
            }

            RequestResult result = jObject.ToObject<RequestResult>();
            return result.acknowledged;
        }

        public async Task<bool> ExistsTypeAsync(string indexName, string typeName)
        {
            string typeUrl = string.Format("{0}/{1}/{2}/_mapping", Node, indexName, typeName);
            return await ExistsTypeAsync(typeUrl);
        }

        protected async Task<bool> ExistsTypeAsync(string typeUrl)
        {
            string responseText = await NetworkClient.GetAsync(typeUrl);
            if (string.IsNullOrEmpty(responseText) == true)
            {
                return false;
            }

            dynamic typeResult = Newtonsoft.Json.JsonConvert.DeserializeObject(responseText);
            JObject jObject = typeResult as JObject;

            if (DetectErrorInfo(typeResult) == true)
            {
                return false;
            }

            return true;
        }

        protected async Task<bool> AddOrUpdateDocumentAsync(string documentUrl, string putData)
        {
            string responseText = await NetworkClient.PutAsync(documentUrl, putData);
            if (string.IsNullOrEmpty(responseText) == true)
            {
                return false;
            }

            dynamic addResult = Newtonsoft.Json.JsonConvert.DeserializeObject(responseText);
            JObject jObject = addResult as JObject;

            if (DetectErrorInfo(addResult) == true)
            {
                return false;
            }

            TypeAddResult result = jObject.ToObject<TypeAddResult>();
            return result._shards.successful == 1;
        }

        protected async Task<JToken> SearchDocumentsAsync(string typeUrl, string putData)
        {
            string responseText = await NetworkClient.PostAsync(typeUrl, putData);
            if (string.IsNullOrEmpty(responseText) == true)
            {
                return null;
            }

            dynamic searchResult = Newtonsoft.Json.JsonConvert.DeserializeObject(responseText);
            JObject jObject = searchResult as JObject;

            if (DetectErrorInfo(searchResult) == true)
            {
                return null;
            }

            SearchResult result = jObject.ToObject<SearchResult>();
            if (result.hits.total == 0)
            {
                return null;
            }

            return jObject.GetValue("hits").SelectToken("hits");
        }
    }
}

이걸 바탕으로 여러분들의 도메인에 맞는 타입들을 맞춰가야 합니다. 그나마 다행이라면 6.x 때부터 Index와 Type이 1:1 관계여서 Type만으로 그냥 Index는 자동화해서 처리할 수 있기 때문에 로직이 약간 가벼워질 수 있습니다. 게다가, 도메인 관련 타입들은 분리해서 dynamic과 함께 JSON.NET의 JToken/JObject 타입의 ToObject 제네릭 메서드를 잘 섞으면 유연하게 처리할 수 있습니다.

using ElasticSearchLib;
using Newtonsoft.Json.Linq;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace BlogSearchLib
{
    public class BlogSearchClient : ElasticSearchClient
    {
        public BlogSearchClient(string server, short port) : base(server, port)
        {
        }

        string GetResurceText(string resourceName)
        {
            return GetResurceText(typeof(BlogSearchClient), resourceName);
        }

        public async Task<bool> AddOrUpdateDocumentAsync(string indexName, string typeName, Article article)
        {
            string documentUrl = string.Format("{0}/{1}/{2}/{3}", Node, indexName, typeName, article.wid);
            string putData = Newtonsoft.Json.JsonConvert.SerializeObject(article);

            return await AddOrUpdateDocumentAsync(documentUrl, putData);
        }

        private string GetQuery(string keyword, int pageFrom, int pageSize)
        {
            string template = GetResurceText("SearchBlog.json");
            string searchOperator = "or";

            if (keyword.IndexOf("and") != -1)
            {
                searchOperator = "and";
                keyword = keyword.Replace("and", "");
            }

            return string.Format(template, pageFrom, pageSize, keyword, searchOperator);
        }

        public async Task<Article[]> SearchDocumentsAsync(string indexName, string typeName, string keyword, int pageFrom = 0, int pageSize = 25)
        {
            List<Article> articles = new List<Article>();

            string typeUrl = string.Format("{0}/{1}/{2}/_search", Node, indexName, typeName);
            string putData = GetQuery(keyword, pageFrom, pageSize);

            JToken jToken = await SearchDocumentsAsync(typeUrl, putData);
            if (jToken == null)
            {
                return articles.ToArray();
            }

            List<Hit> hits = jToken.ToObject<List<Hit>>();

            articles.AddRange(from hit in hits
                              select hit._source);

            return articles.ToArray();
        }

        public async Task<Article> GetDocumentAsync(string indexName, string typeName, int key)
        {
            string documentUrl = string.Format("{0}/{1}/{2}/{3}", Node, indexName, typeName, key);

            string responseText = await NetworkClient.GetAsync(documentUrl);
            if (string.IsNullOrEmpty(responseText) == true)
            {
                return null;
            }

            dynamic indexResult = Newtonsoft.Json.JsonConvert.DeserializeObject(responseText);

            if (indexResult.found == false)
            {
                return null;
            }

            JObject jObject = indexResult as JObject;

            return jObject.GetValue("_source").ToObject<Article>();
        }

        public async Task<bool> CreateBlogTypeAsync(string indexName, string typeName)
        {
            if (await CreateIndexAsync(indexName) == false)
            {
                return false;
            }

            string putData = string.Format(GetResurceText("CreateBlogType.json"), typeName);
            return await CreateTypeAsync(indexName, typeName, putData);
        }

    }
}

그래서 다음과 같이 간단하게 CRUD 작업을 할 수 있습니다.

using BlogSearchLib;
using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    class Program
    {
        const string Server = "localhost";
        const short Port = 9200;

        const string _typeName = "article";
        const string _indexName = "article_idx";

        static async Task Main(string[] args)
        {
            BlogSearchClient client = new BlogSearchClient(Server, Port);

            if (await client.ConnectAsync("6.1.1.0") == false)
            {
                Console.WriteLine("Can't conncted");
                return;
            }

            await CreateType(client);
            Console.WriteLine();
            await AddSamples(client);
            Console.WriteLine();
            await GetSamples(client);
            Console.WriteLine();

            Thread.Sleep(5000);
            await SearchSamples(client);
            Console.WriteLine();

            Console.WriteLine("Press any key to exit...");
            Console.ReadLine();
        }

        private static async Task SearchSamples(BlogSearchClient client)
        {
            Console.WriteLine("[Keywrods: gacutil]");
            {
                Article [] articles = await client.SearchDocumentsAsync(_indexName, _typeName, "gacutil");

                foreach (Article article in articles)
                {
                    Console.WriteLine("Search Document: " + article.contents);
                }
            }
            Console.WriteLine();

            Console.WriteLine("[Keywrods: gacutil 한국어 테스트]");
            {
                Article[] articles = await client.SearchDocumentsAsync(_indexName, _typeName, "gacutil 한국어 테스트", 0, 2);

                foreach (Article article in articles)
                {
                    Console.WriteLine("Search Document: " + article.contents);
                }
            }
            Console.WriteLine();

            Console.WriteLine("[Keywrods: gacutil and 닷넷]");
            {
                Article[] articles = await client.SearchDocumentsAsync(_indexName, _typeName, "gacutil and 닷넷");

                foreach (Article article in articles)
                {
                    Console.WriteLine("Search Document: " + article.contents);
                }
            }
            Console.WriteLine();

            Console.WriteLine("[Keywrods: gacutil and 닷넷DLL]");
            {
                Article[] articles = await client.SearchDocumentsAsync(_indexName, _typeName, "gacutil and 닷넷DLL");

                foreach (Article article in articles)
                {
                    Console.WriteLine("Search Document: " + article.contents);
                }
            }
            Console.WriteLine();
        }

        private static async Task GetSamples(BlogSearchClient client)
        {
            {
                Article article = await client.GetDocumentAsync(_indexName, _typeName, 1);
                Console.WriteLine("Document 1: " + article.contents);
            }

            {
                Article article = await client.GetDocumentAsync(_indexName, _typeName, 2);
                Console.WriteLine("Document 2: " + article.contents);
            }

            {
                Article article = await client.GetDocumentAsync(_indexName, _typeName, 3);
                Console.WriteLine("Document 3: " + article.contents);
            }

            {
                Article article = await client.GetDocumentAsync(_indexName, _typeName, 4);
                Console.WriteLine("Expected null: " + article);
            }
        }

        private static async Task AddSamples(BlogSearchClient client)
        {
            {
                Article article = new Article
                {
                    contents = "gacutil.exe를 실행해 <a href='dotnet'>닷넷</a>DLL을 GAC에 등록하려 할 때 다음과 같은 식의 오류가 발생한다면",
                    registered = DateTime.Now,
                    wid = 1,
                    writer = "tester1",
                };

                Console.WriteLine("Added: " + await client.AddOrUpdateDocumentAsync(_indexName, _typeName, article));
            }

            {
                Article article = new Article
                {
                    contents = "한국어를 처리하는 예시입니닼ㅋㅋ",
                    registered = DateTime.Now,
                    wid = 2,
                    writer = "tester2",
                };

                Console.WriteLine("Added: " + await client.AddOrUpdateDocumentAsync(_indexName, _typeName, article));
            }

            {
                Article article = new Article
                {
                    contents = "테스트 이미지<img alt=\"test\">입니다",
                    registered = DateTime.Now,
                    wid = 3,
                    writer = "tester3",
                };

                Console.WriteLine("Added: " + await client.AddOrUpdateDocumentAsync(_indexName, _typeName, article));
            }
        }

        private static async Task CreateType(BlogSearchClient client)
        {
            if (await client.ExistsTypeAsync(_indexName, _typeName) == true)
            {
                Console.Write("deleting old articles type... ");
                bool deleteOldType = await client.RemoveIndexAsync(_typeName);
                Console.WriteLine(deleteOldType);
            }
            else
            {
                Console.Write("NO TYPE: articles - ");
                Console.Write(client.LastErrorInfo.status + " - ");
                Console.WriteLine(client.LastErrorInfo.error.reason);
            }

            Console.Write("creating myorg/articles type... ");
            bool createTypeResult = await client.CreateBlogTypeAsync(_indexName, _typeName);
            Console.WriteLine(createTypeResult);
        }
    }
}

물론, 살을 더 붙여나가야겠지만 기본은 이 정도만 있으면 충분할 것입니다. ^^

(첨부 파일은 이 글의 프로젝트를 포함합니다.)




이하 시행착오입니다.

우선, Elasticsearch는 기본적으로 127.0.0.1에만 소켓 바인딩을 합니다.

[2018-08-26T01:32:24,436][INFO ][o.e.n.Node               ] [] initializing ...
[2018-08-26T01:32:24,550][INFO ][o.e.e.NodeEnvironment    ] [FGFsQ64] using [1] data paths, mounts [[New Volume (F:)]], net usable_space [282.8gb], net total_space [931.5gb], types [NTFS]
...[생략]...
[2018-08-26T01:32:30,030][INFO ][o.e.t.TransportService   ] [FGFsQ64] publish_address {127.0.0.1:9300}, bound_addresses {127.0.0.1:9300}, {[::1]:9300}
[2018-08-26T01:32:33,103][INFO ][o.e.c.s.MasterService    ] [FGFsQ64] zen-disco-elected-as-master ([0] nodes joined), reason: new_master {FGFsQ64}{FGFsQ64DRmyG6k9JXVyBPQ}{paFlb_XkQ4a0TiADrp70tg}{127.0.0.1}{127.0.0.1:9300}
...[생략]...

즉, 외부 서비스는 할 수 없는 상태인데 이것을 바꾸려면 yml 설정 파일을 열어,

[설치폴더]\config\elasticsearch.yml

network.host 값을 바꿔주면 됩니다.

network.host: 202.254.158.1

그리고 JAVA의 특성상 힙 메모리를 지정하는데 elasticsearch의 경우 기본 1GB를 점유하도록 설정되어 있습니다. 가벼운 VM에서 시작하는 경우 이것은 꽤나 부담이 될 수 있는 수치이므로 VM 사이즈의 조정이 필요할 수 있습니다. (아니면 JVM 힙의 크기를 줄이거나.)




윈도우 환경에서 서비스하고 있는 경우 당연히 NT 서비스로 등록을 해야 하는데요, 명령행에서 elasticsearch.bat을 실행했던 바로 그 폴더에 포함된 elasticsearch-service.bat 파일을 다음과 같이 실행하면 됩니다.

elasticsearch-service.bat install

그런데, JAVA_HOME을 알아야 하므로 이것을 elasticsearch-service.bat 파일에 등록해 주어도 되긴 합니다.

@echo off

SET JAVA_HOME=C:\Program Files\Java\jdk1.8.0_181
...[생략]...

그럼 다음과 같은 설정으로 시스템에 등록하는데,

서비스 이름: elasticsearch-service-x64
유형: Manual
상태: Stopped
경로: C:\elasticsearch\bin\elasticsearch-service-x64.exe //RS//elasticsearch-service-x64

따라서 "Automatic"으로 바꾼 후 명시적으로 시작 상태로 만들어야 합니다. 그런데, 실제로 시작해 보면 다음과 같이 오류가 발생할 수 있습니다.

Windows could not start the Elasticsearch 6.1.1 (elasticsearch-service-x64) on Local Computer. For more information, review the System Event Log. If this is a non-Microsoft service, contact the service vendor, and refer to service-specific error code 1.


이벤트 로그를 살펴봐도 별다른 정보가 없고,

Log Name:      System
Source:        Service Control Manager
Date:          8/26/2018 8:49:09 AM
Event ID:      7024
Task Category: None
Level:         Error
Keywords:      Classic
User:          N/A
Computer:      vm1
Description:
The Elasticsearch 6.1.1 (elasticsearch-service-x64) service terminated with the following service-specific error: 
Incorrect function.

대신 elasticsearch 설치 폴더의 logs 폴더에 있는 로그 파일을 보면,

[2018-08-26 08:51:09] [info]  [ 3364] Commons Daemon procrun (1.0.15.0 64-bit) started
[2018-08-26 08:51:09] [info]  [ 3364] Running 'elasticsearch-service-x64' Service...
[2018-08-26 08:51:09] [info]  [ 4268] Starting service...
[2018-08-26 08:51:09] [error] [ 4268] Failed creating java %JAVA_HOME%\jre\bin\server\jvm.dll
[2018-08-26 08:51:09] [error] [ 4268] The system cannot find the path specified.
[2018-08-26 08:51:09] [error] [ 4268] ServiceStart returned 1
[2018-08-26 08:51:09] [error] [ 4268] The system cannot find the path specified.
[2018-08-26 08:51:09] [info]  [ 3364] Run service finished.
[2018-08-26 08:51:10] [info]  [ 3364] Commons Daemon procrun finished

위와 같이 JAVA_HOME 변수를 사용하고 있는 것을 볼 수 있습니다. 따라서 NT 서비스로 Elasticsearch를 구동하려면 어차피 전역 시스템 환경 변수에 JAVA_HOME을 등록해야 합니다.




영문 윈도우에서 한글 데이터를 쓰는 경우 WebClient 타입으로 코딩한 경우 다음과 같은 식의 오류가 발생합니다.

Caused by: com.fasterxml.jackson.core.JsonParseException: Invalid UTF-8 middle byte 0xcc
 at [Source: org.elasticsearch.common.bytes.BytesReference$MarkSupportingStreamInputWrapper@750bcb09; line: 1, column: 189]
        at com.fasterxml.jackson.core.JsonParser._constructError(JsonParser.java:1702) ~[jackson-core-2.8.10.jar:2.8.10]
        at com.fasterxml.jackson.core.base.ParserMinimalBase._reportError(ParserMinimalBase.java:558) ~[jackson-core-2.8.10.jar:2.8.10]
        at com.fasterxml.jackson.core.json.UTF8StreamJsonParser._reportInvalidOther(UTF8StreamJsonParser.java:3550) ~[jackson-core-2.8.10.jar:2.8.10]
        at com.fasterxml.jackson.core.json.UTF8StreamJsonParser._reportInvalidOther(UTF8StreamJsonParser.java:3557) ~[jackson-core-2.8.10.jar:2.8.10]
        at com.fasterxml.jackson.core.json.UTF8StreamJsonParser._decodeUtf8_2(UTF8StreamJsonParser.java:3327) ~[jackson-core-2.8.10.jar:2.8.10]
        at com.fasterxml.jackson.core.json.UTF8StreamJsonParser._finishString2(UTF8StreamJsonParser.java:2517) ~[jackson-core-2.8.10.jar:2.8.10]
        at com.fasterxml.jackson.core.json.UTF8StreamJsonParser._finishAndReturnString(UTF8StreamJsonParser.java:2469) ~[jackson-core-2.8.10.jar:2.8.10]
        at com.fasterxml.jackson.core.json.UTF8StreamJsonParser.getText(UTF8StreamJsonParser.java:315) ~[jackson-core-2.8.10.jar:2.8.10]
        at org.elasticsearch.common.xcontent.json.JsonXContentParser.text(JsonXContentParser.java:83) ~[elasticsearch-6.1.1.jar:6.1.1]
        at org.elasticsearch.common.xcontent.support.AbstractXContentParser.textOrNull(AbstractXContentParser.java:237) ~[elasticsearch-6.1.1.jar:6.1.1]
        at org.elasticsearch.index.mapper.TextFieldMapper.parseCreateField(TextFieldMapper.java:345) ~[elasticsearch-6.1.1.jar:6.1.1]
        at org.elasticsearch.index.mapper.FieldMapper.parse(FieldMapper.java:297) ~[elasticsearch-6.1.1.jar:6.1.1]
        ... 38 more

왜냐하면 WebClient는 기본 인코딩을 시스템에 설정된 언어로 처리하기 때문에 영문 윈도우인 경우 한글 처리가 안 되기 때문입니다. 그래서 다음과 같은 식으로 반드시 Encoding을 설정해야 합니다.

public string Put(string query, string putData)
{
    WebClient wc = new WebClient();
    wc.Headers["Content-Type"] = "application/json; charset=UTF-8";
    wc.Encoding = Encoding.UTF8;
    try
    {
        return wc.UploadString(query, "PUT", putData);
    }
    catch (System.Net.WebException e)
    {
        // e...
    }
}




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]





[최초 등록일: ]
[최종 수정일: 8/29/2018 ]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer@outlook.com

비밀번호

댓글 쓴 사람
 




1  2  3  4  5  6  7  8  9  10  11  12  [13]  14  15  ...
NoWriterDateCnt.TitleFile(s)
11705정성태9/29/20181776개발 환경 구성: 399. Synology NAS(DS216+II)에 gcc 컴파일러 설치
11704정성태9/29/20182169기타: 73. Synology NAS 신호음(beep) 끄기 [1]파일 다운로드1
11703정성태10/16/20181446개발 환경 구성: 398. Blazor 환경 구성 후 빌드 속도가 너무 느리다면? [1]
11702정성태9/26/2018888사물인터넷: 44. 넷두이노(Netduino)의 네트워크 설정 방법
11701정성태9/26/20181236개발 환경 구성: 397. 공유기를 일반 허브로 활용하는 방법파일 다운로드1
11700정성태9/21/20181100Graphics: 25. Unity - shader의 직교 투영(Orthographic projection) 행렬(UNITY_MATRIX_P)을 수작업으로 구성
11699정성태9/21/2018987오류 유형: 488. Add-AzureAccount 실행 시 "No subscriptions are associated with the logged in account in Azure Service Management (RDFE)." 오류
11698정성태9/21/20181135오류 유형: 487. 윈도우 성능 데이터를 원격 SQL에 저장하는 경우 "Call to SQLAllocConnect failed with %1." 오류 발생
11697정성태9/20/20181094Graphics: 24. Unity - unity_CameraWorldClipPlanes 내장 변수 의미
11696정성태9/19/20181138.NET Framework: 793. C# - REST API를 이용해 NuGet 저장소 제어파일 다운로드1
11695정성태9/21/20181622Graphics: 23. Unity - shader의 원근 투영(Perspective projection) 행렬(UNITY_MATRIX_P)을 수작업으로 구성
11694정성태9/17/2018845오류 유형: 486. nuget push 호출 시 405 Method Not Allowed 오류 발생
11693정성태9/16/20181432VS.NET IDE: 128. Unity - shader 코드 디버깅 방법
11692정성태9/18/20181413Graphics: 22. Unity - shader의 Camera matrix(UNITY_MATRIX_V)를 수작업으로 구성
11691정성태9/13/20181002VS.NET IDE: 127. Visual C++ / x64 환경에서 inline-assembly를 매크로 어셈블리로 대체하는 방법 - 두 번째 이야기
11690정성태9/13/20181552사물인터넷: 43. 555 타이머의 단안정 모드파일 다운로드1
11689정성태9/13/20181037VS.NET IDE: 126. 디컴파일된 소스에 탐색을 사용하도록 설정(Enable navigation to decompiled sources)
11688정성태9/11/20181026오류 유형: 485. iisreset - The data is invalid. (2147942413, 8007000d) 오류 발생
11687정성태9/11/2018927사물인터넷: 42. 사물인터넷 - 트랜지스터 다중 전압 테스트파일 다운로드1
11686정성태9/8/2018902사물인터넷: 41. 다중 전원의 소스를 가진 회로파일 다운로드1
11685정성태9/6/20181179사물인터넷: 40. 이어폰 소리를 capacitor로 필터링파일 다운로드1
11684정성태9/6/20181553개발 환경 구성: 396. pagefile.sys를 비활성화시켰는데도 working set 메모리가 줄어드는 이유파일 다운로드1
11683정성태9/5/20181067개발 환경 구성: 395. Azure Web App의 이벤트 로그를 확인하는 방법
11682정성태9/5/20181076오류 유형: 484. Fakes를 포함한 단위 테스트 프로젝트를 빌드 시 CS1729 관련 오류 발생
11681정성태9/5/20181873Windows: 149. 다른 컴퓨터의 윈도우 이벤트 로그를 구독하는 방법 [2]
11680정성태9/2/20181467Graphics: 21. shader - _Time 내장 변수를 이용한 UV 변동 효과파일 다운로드1
1  2  3  4  5  6  7  8  9  10  11  12  [13]  14  15  ...