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

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)
11780정성태11/17/20181789오류 유형: 503. vcpkg install bzip2 빌드 에러 - "Error: Building package bzip2:x86-windows failed with: BUILD_FAILED"
11779정성태11/17/20181879개발 환경 구성: 421. vcpkg 업데이트 [1]
11778정성태11/14/20181259.NET Framework: 803. UWP 앱에서 한 컴퓨터(localhost, 127.0.0.1) 내에서의 소켓 연결
11777정성태11/13/20181853오류 유형: 502. Your project does not reference "..." framework. Add a reference to "..." in the "TargetFrameworks" property of your project file and then re-run NuGet restore.
11776정성태11/13/20181072.NET Framework: 802. Windows에 로그인한 계정이 마이크로소프트의 계정인지, 로컬 계정인지 알아내는 방법
11775정성태11/18/20182297Graphics: 31. .NET으로 구현하는 OpenGL (6) - Texturing파일 다운로드1
11774정성태11/13/20181514Graphics: 30. .NET으로 구현하는 OpenGL (4), (5) - Shader파일 다운로드1
11773정성태11/7/20181317Graphics: 29. .NET으로 구현하는 OpenGL (3) - Index Buffer파일 다운로드1
11772정성태11/6/20181711Graphics: 28. .NET으로 구현하는 OpenGL (2) - VAO, VBO파일 다운로드1
11771정성태11/5/20181366사물인터넷: 56. Audio Jack 커넥터의 IR 적외선 송신기 - 두 번째 이야기 [1]
11770정성태11/5/20183036Graphics: 27. .NET으로 구현하는 OpenGL (1) - OpenGL.Net 라이브러리파일 다운로드1
11769정성태11/5/20181139오류 유형: 501. 프로젝트 msbuild Publish 후 connectionStrings의 문자열이 $(ReplacableToken_...)로 바뀌는 문제
11768정성태11/2/20181386.NET Framework: 801. SOIL(Simple OpenGL Image Library) - Native DLL 및 .NET DLL 제공
11767정성태11/20/20181689사물인터넷: 55. New NodeMcu v3(ESP8266)의 IR LED (적외선 송신) 제어파일 다운로드1
11766정성태11/30/20182065사물인터넷: 54. 아두이노 환경에서의 JSON 파서(ArduinoJson) 사용법
11765정성태10/29/20181423개발 환경 구성: 420. Visual Studio Code - Arduino Board Manager를 이용한 사용자 정의 보드 선택
11764정성태10/26/20182052개발 환경 구성: 419. MIT 라이선스로 무료 공개된 Detours API 후킹 라이브러리
11763정성태10/25/20181572사물인터넷: 53. New NodeMcu v3(ESP8266)의 https 통신
11762정성태10/25/20182273사물인터넷: 52. New NodeMcu v3(ESP8266)의 http 통신파일 다운로드1
11761정성태10/25/20181713Graphics: 26. 임의 축을 기반으로 3D 벡터 회전파일 다운로드1
11760정성태10/24/20181052개발 환경 구성: 418. Azure - Runbook 내에서 또 다른 Runbook 스크립트를 실행
11759정성태10/24/20181127개발 환경 구성: 417. Azure - Runbook에서 사용할 수 있는 다양한 메서드를 위한 부가 Module 추가
11758정성태6/7/20191548.NET Framework: 800. C# - Azure REST API 사용을 위한 인증 획득 [3]파일 다운로드1
11757정성태10/23/20181579개발 환경 구성: 416. Visual Studio 2017을 이용한 아두이노 프로그램 개발(및 디버깅)
11756정성태10/19/20181378오류 유형: 500. Visual Studio Code의 아두이노 프로그램 개발 시 인텔리센스가 안 된다면?
11755정성태10/19/20182754오류 유형: 499. Visual Studio Code extension for Arduino - #include errors detected. [1]
1  2  3  4  5  6  7  8  9  10  11  12  [13]  14  15  ...