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

(시리즈 글이 5개 있습니다.)
개발 환경 구성: 392. 윈도우 환경에서 curl.exe를 이용한 elasticsearch 6.x 기본 사용법
; https://www.sysnet.pe.kr/2/0/11663

개발 환경 구성: 393. 윈도우 환경에서 elasticsearch의 한글 형태소 분석기 설치
; https://www.sysnet.pe.kr/2/0/11664

개발 환경 구성: 394. 윈도우 환경에서 elasticsearch의 한글 블로그 검색 인덱스 구성
; https://www.sysnet.pe.kr/2/0/11669

.NET Framework: 791. C# - ElasticSearch를 위한 Client 라이브러리 제작
; https://www.sysnet.pe.kr/2/0/11676

개발 환경 구성: 507. Elasticsearch 6.6부터 기본 추가된 한글 형태소 분석기 노리(nori) 사용법
; https://www.sysnet.pe.kr/2/0/12309




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 at outlook.com

비밀번호

댓글 작성자
 




1  2  3  4  5  6  7  8  9  [10]  11  12  13  14  15  ...
NoWriterDateCnt.TitleFile(s)
13372정성태6/15/20233104개발 환경 구성: 682. SQL Server TLS 통신을 위해 사용되는 키 길이 확인 방법
13371정성태6/15/20233112개발 환경 구성: 681. openssl - 인증서 버전(V1 / V3)
13370정성태6/14/20233288개발 환경 구성: 680. C# - Ubuntu + Microsoft.Data.SqlClient + SQL Server 2008 R2 연결 방법 - TLS 1.2 지원
13369정성태6/13/20233090개발 환경 구성: 679. PyCharm(을 비롯해 JetBrains에 속한 여타) IDE에서 내부 Window들의 탭이 없어진 경우
13368정성태6/13/20233222개발 환경 구성: 678. openssl로 생성한 인증서를 SQL Server의 암호화 인증서로 설정하는 방법
13367정성태6/10/20233324오류 유형: 864. openssl로 만든 pfx 인증서를 Windows Server 2016 이하에서 등록 시 "The password you entered is incorrect" 오류 발생
13366정성태6/10/20233114.NET Framework: 2128. C# - 윈도우 시스템에서 지원하는 암호화 목록(Cipher Suites) 나열파일 다운로드1
13365정성태6/8/20232888오류 유형: 863. MODIFY FILE encountered operating system error 112(failed to retrieve text for this error. Reason: 15105)
13364정성태6/8/20233669.NET Framework: 2127. C# - Ubuntu + Microsoft.Data.SqlClient + SQL Server 2008 R2 연결 방법 [1]
13363정성태6/7/20233234스크립트: 49. 파이썬 - "Transformers (신경망 언어모델 라이브러리) 강좌" - 1장 2절 코드 실행 결과
13362정성태6/1/20233157.NET Framework: 2126. C# - 서버 측의 요청 제어 (Microsoft.AspNetCore.RateLimiting)파일 다운로드1
13361정성태5/31/20233631오류 유형: 862. Facebook - ASP.NET/WebClient 사용 시 graph.facebook.com/me 호출에 대해 403 Forbidden 오류
13360정성태5/31/20233026오류 유형: 861. WSL/docker - failed to start shim: start failed: io.containerd.runc.v2: create new shim socket
13359정성태5/19/20233346오류 유형: 860. Docker Desktop - k8s 초기화 무한 반복한다면?
13358정성태5/17/20233644.NET Framework: 2125. C# - Semantic Kernel의 Semantic Memory 사용 예제 [1]파일 다운로드1
13357정성태5/16/20233460.NET Framework: 2124. C# - Semantic Kernel의 Planner 사용 예제파일 다운로드1
13356정성태5/15/20233758DDK: 10. Device Driver 테스트 설치 관련 오류 (Code 37, Code 31) 및 인증서 관련 정리
13355정성태5/12/20233681.NET Framework: 2123. C# - Semantic Kernel의 ChatGPT 대화 구현 [1]파일 다운로드1
13354정성태5/12/20233954.NET Framework: 2122. C# - "Use Unicode UTF-8 for worldwide language support" 설정을 한 경우, 한글 입력이 '\0' 문자로 처리
13352정성태5/12/20233566.NET Framework: 2121. C# - Semantic Kernel의 대화 문맥 유지파일 다운로드1
13351정성태5/11/20234068VS.NET IDE: 185. Visual Studio - 원격 Docker container 내에 실행 중인 응용 프로그램에 대한 디버깅 [1]
13350정성태5/11/20233318오류 유형: 859. Windows Date and Time - Unable to continue. You do not have permission to perform this task
13349정성태5/11/20233645.NET Framework: 2120. C# - Semantic Kernel의 Skill과 Function 사용 예제파일 다운로드1
13348정성태5/10/20233567.NET Framework: 2119. C# - Semantic Kernel의 "Basic Loading of the Kernel" 예제
13347정성태5/10/20233929.NET Framework: 2118. C# - Semantic Kernel의 Prompt chaining 예제파일 다운로드1
13346정성태5/10/20233775오류 유형: 858. RDP 원격 환경과 로컬 PC 간의 Ctrl+C, Ctrl+V 복사가 안 되는 문제
1  2  3  4  5  6  7  8  9  [10]  11  12  13  14  15  ...