<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>hungry_foolish.log</title>
        <link>https://velog.io/</link>
        <description>늦은 것 같지만 이제부터라도 차근차근 하나씩 정리하기</description>
        <lastBuildDate>Mon, 17 Jul 2023 02:36:45 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <copyright>Copyright (C) 2019. hungry_foolish.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/hungry_foolish" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[gson 참고]]></title>
            <link>https://velog.io/@hungry_foolish/gson-%EC%B0%B8%EA%B3%A0</link>
            <guid>https://velog.io/@hungry_foolish/gson-%EC%B0%B8%EA%B3%A0</guid>
            <pubDate>Mon, 17 Jul 2023 02:36:45 GMT</pubDate>
            <description><![CDATA[<p><a href="https://hianna.tistory.com/629">https://hianna.tistory.com/629</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[sample-launch.json]]></title>
            <link>https://velog.io/@hungry_foolish/sample-launch.json</link>
            <guid>https://velog.io/@hungry_foolish/sample-launch.json</guid>
            <pubDate>Sun, 25 Jun 2023 09:00:30 GMT</pubDate>
            <description><![CDATA[<pre><code class="language-json">{
    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    &quot;version&quot;: &quot;0.2.0&quot;,
    &quot;configurations&quot;: [
        {
            &quot;type&quot;: &quot;java&quot;,
            &quot;name&quot;: &quot;Current File&quot;,
            &quot;request&quot;: &quot;launch&quot;,
            &quot;mainClass&quot;: &quot;${file}&quot;
        },
        {
            &quot;type&quot;: &quot;java&quot;,
            &quot;name&quot;: &quot;SUB1&quot;,
            &quot;request&quot;: &quot;launch&quot;,
            &quot;mainClass&quot;: &quot;com.test.Main&quot;,
            &quot;projectName&quot;: &quot;SUB1&quot;,
            &quot;cwd&quot;: &quot;${workspaceFolder}/SUB1&quot;
        },
        {
            &quot;type&quot;: &quot;java&quot;,
            &quot;name&quot;: &quot;SUB2&quot;,
            &quot;request&quot;: &quot;launch&quot;,
            &quot;mainClass&quot;: &quot;com.test.Main&quot;,
            &quot;projectName&quot;: &quot;SUB2&quot;,
            &quot;cwd&quot;: &quot;${workspaceFolder}/SUB2&quot;
        },
        {
            &quot;type&quot;: &quot;java&quot;,
            &quot;name&quot;: &quot;SUB3&quot;,
            &quot;request&quot;: &quot;launch&quot;,
            &quot;mainClass&quot;: &quot;com.test.Main&quot;,
            &quot;projectName&quot;: &quot;SUB3&quot;,
            &quot;cwd&quot;: &quot;${workspaceFolder}/SUB3&quot;
        },
        {
            &quot;type&quot;: &quot;java&quot;,
            &quot;name&quot;: &quot;SUB4&quot;,
            &quot;request&quot;: &quot;launch&quot;,
            &quot;mainClass&quot;: &quot;com.test.Main&quot;,
            &quot;projectName&quot;: &quot;SUB4&quot;,
            &quot;cwd&quot;: &quot;${workspaceFolder}/SUB4&quot;
        },
        {
            &quot;type&quot;: &quot;java&quot;,
            &quot;name&quot;: &quot;SUB5&quot;,
            &quot;request&quot;: &quot;launch&quot;,
            &quot;mainClass&quot;: &quot;com.test.Main&quot;,
            &quot;projectName&quot;: &quot;SUB5&quot;,
            &quot;cwd&quot;: &quot;${workspaceFolder}/SUB5&quot;
        }
    ]
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[java thread]]></title>
            <link>https://velog.io/@hungry_foolish/java-thread</link>
            <guid>https://velog.io/@hungry_foolish/java-thread</guid>
            <pubDate>Mon, 19 Jun 2023 01:52:57 GMT</pubDate>
            <description><![CDATA[<p><a href="https://wikidocs.net/230">https://wikidocs.net/230</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[python] poetry에서 ssl 에러 발생할 때]]></title>
            <link>https://velog.io/@hungry_foolish/python-poetry%EC%97%90%EC%84%9C-ssl-%EC%97%90%EB%9F%AC-%EB%B0%9C%EC%83%9D%ED%95%A0-%EB%95%8C</link>
            <guid>https://velog.io/@hungry_foolish/python-poetry%EC%97%90%EC%84%9C-ssl-%EC%97%90%EB%9F%AC-%EB%B0%9C%EC%83%9D%ED%95%A0-%EB%95%8C</guid>
            <pubDate>Mon, 03 Oct 2022 02:04:45 GMT</pubDate>
            <description><![CDATA[<p>pip install할 때 ssl 에러가 발생하면 아래와 같이 <code>--trusted-host</code> 옵션을 추가해서 해결 가능한데,</p>
<pre><code class="language-bash">pip --trusted-host pypi.org --trusted-host files.pythonhosted.org install [패키지명]</code></pre>
<p>poetry에서는 옵션을 추가해서 ssl 에러를 넘어갈 방법이 없다. </p>
<p>poetry는 아래와 같은 방법으로 가능하다.</p>
<p>우선 <code>pip --version</code>을 실행해서 패키지 설치 경로를 확인 후</p>
<p>[패키지 설치 경로]/pip/_vendor/requests/sessions.py 파일의 아래 코드에서</p>
<pre><code class="language-python">        #: SSL Verification default.
        #: Defaults to `True`, requiring requests to verify the TLS certificate at the
        #: remote end.
        #: If verify is set to `False`, requests will accept any TLS certificate
        #: presented by the server, and will ignore hostname mismatches and/or
        #: expired certificates, which will make your application vulnerable to
        #: man-in-the-middle (MitM) attacks.
        #: Only set this to `False` for testing.
        self.verify = True

        #: SSL client certificate default, if String, path to ssl client
        #: cert file (.pem). If Tuple, (&#39;cert&#39;, &#39;key&#39;) pair.
        self.cert = None</code></pre>
<p><code>self.verify = True</code>를 <code>False</code>로 변경한다.</p>
<p>위 방법으로 해결 안되는 경우 site-packages/requests/adapters.py line 394에 send() 함수가 있는데 아래와 같이 한줄을 추가해준다.</p>
<pre><code class="language-python">    def send(self, request, stream=False, timeout=None, verify=True, cert=None, proxies=None):
        &quot;&quot;&quot;Sends PreparedRequest object. Returns Response object.

        :rtype: requests.Response
        &quot;&quot;&quot;

        verify = False        # ==&gt; 추가

        try:
            conn = self.get_connection(request.url, proxies)
        except LocationValueError as e:
            raise InvalidURL(e, request=request)</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[urlclassloader]]></title>
            <link>https://velog.io/@hungry_foolish/urlclassloader</link>
            <guid>https://velog.io/@hungry_foolish/urlclassloader</guid>
            <pubDate>Mon, 11 Jul 2022 12:26:19 GMT</pubDate>
            <description><![CDATA[<p>urlclassloader
<a href="https://daily-study.tistory.com/3?category=697146">https://daily-study.tistory.com/3?category=697146</a></p>
<p>Loading Properties
<a href="https://daily-study.tistory.com/7?category=697146">https://daily-study.tistory.com/7?category=697146</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[thread-safe map, list]]></title>
            <link>https://velog.io/@hungry_foolish/thread-safe-map-list</link>
            <guid>https://velog.io/@hungry_foolish/thread-safe-map-list</guid>
            <pubDate>Mon, 13 Jun 2022 11:43:08 GMT</pubDate>
            <description><![CDATA[<pre><code class="language-java">Map&lt;K,V&gt; map = new ConcurrentHashMap&lt;K,V&gt;();

Queue&lt;E&gt; queue = new ConcurrentLinkedQueue&lt;E&gt;();</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[java producer/consumer 구현]]></title>
            <link>https://velog.io/@hungry_foolish/java-producerconsumer-%EA%B5%AC%ED%98%84</link>
            <guid>https://velog.io/@hungry_foolish/java-producerconsumer-%EA%B5%AC%ED%98%84</guid>
            <pubDate>Sun, 12 Jun 2022 15:15:21 GMT</pubDate>
            <description><![CDATA[<p><a href="https://roytuts.com/busy-waiting-or-spinning-in-java-multi-threading/">https://roytuts.com/busy-waiting-or-spinning-in-java-multi-threading/</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[tail]]></title>
            <link>https://velog.io/@hungry_foolish/tail</link>
            <guid>https://velog.io/@hungry_foolish/tail</guid>
            <pubDate>Sun, 12 Jun 2022 09:36:44 GMT</pubDate>
            <description><![CDATA[<pre><code class="language-java">public class Tail {

    long interval;
    String filepath;

    public void run() {
        long filePointer = 0;

        while (true) {
            Utils.sleep(interval);
            try (RandomAccessFile file = new RandomAccessFile(filepath, &quot;r&quot;)) {
                long fileLength = file.length();

                if (fileLength &gt; filePointer) {
                    file.seek(filePointer);
                    String line = null;
                    while ((line = file.readLine()) != null) {
                        // todo
                    }
                    filePointer = file.getFilePointer();
                }
            } catch (FileNotFoundException e) {
                ;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[TcpServer / Client]]></title>
            <link>https://velog.io/@hungry_foolish/TcpServer-Client</link>
            <guid>https://velog.io/@hungry_foolish/TcpServer-Client</guid>
            <pubDate>Sun, 12 Jun 2022 09:35:33 GMT</pubDate>
            <description><![CDATA[<pre><code class="language-java">public class TcpServer {

    public void run(int port) {
        try (ServerSocket serverSocket = new ServerSocket(port)) {
            System.out.println(&quot;Server is listening on port &quot; + port);

            while (true) {
                Socket socket = serverSocket.accept();
                System.out.println(&quot;[ &quot; + socket.getInetAddress() + &quot; ] client connected&quot;);
                OutputStream output = socket.getOutputStream();
                PrintWriter writer = new PrintWriter(output, true);
                writer.println(new Date().toString());

                InputStream input = socket.getInputStream();
                BufferedReader reader = new BufferedReader(new InputStreamReader(input));
                System.out.println(&quot;###### msg : &quot; + reader.readLine());
            }

        } catch (IOException ex) {
            System.out.println(&quot;Server exception: &quot; + ex.getMessage());
            ex.printStackTrace();
        }
    }
}</code></pre>
<pre><code class="language-java">public class TcpClient {

    public void run(String hostname, int port, String data) {
        try (Socket socket = new Socket(hostname, port)) {
            OutputStream out = socket.getOutputStream();
            out.write(data.getBytes());
            InputStream input = socket.getInputStream();
            BufferedReader reader = new BufferedReader(new InputStreamReader(input));

            String time = reader.readLine();
            System.out.println(time);

        } catch (UnknownHostException ex) {
            System.out.println(&quot;Server not found: &quot; + ex.getMessage());

        } catch (IOException ex) {
            System.out.println(&quot;I/O error: &quot; + ex.getMessage());
        }
    }
}
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[잡동사니]]></title>
            <link>https://velog.io/@hungry_foolish/%EC%9E%A1%EB%8F%99%EC%82%AC%EB%8B%88</link>
            <guid>https://velog.io/@hungry_foolish/%EC%9E%A1%EB%8F%99%EC%82%AC%EB%8B%88</guid>
            <pubDate>Sat, 11 Jun 2022 10:53:54 GMT</pubDate>
            <description><![CDATA[<p>Utils.java</p>
<pre><code class="language-java">import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import java.util.stream.Stream;

public class Utils {

    public static void printCurrentPath() {
        String currentDir = System.getProperty(&quot;user.dir&quot;);
        System.out.println(&quot;Current dir using System:&quot; + currentDir);
    }

    public static void sleep(long ms) {
        try {
            Thread.sleep(ms);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

/*
    public static String toJson(Object o) {
        return new Gson().toJson(o);
    }

    public static &lt;T&gt; T fromJson(String s, Class&lt;T&gt; cls) {
        return new Gson().fromJson(s, cls);
    }
*/
    public static String getUuid() {
        return UUID.randomUUID().toString();
    }

    // run external application
    public static void execute(String... args) throws IOException {
        Process process = new ProcessBuilder(args).start();
        InputStream is = process.getInputStream();
        InputStreamReader isr = new InputStreamReader(is);
        BufferedReader br = new BufferedReader(isr);
        String line;

        System.out.printf(&quot;Output of running %s is:&quot;, Arrays.toString(args));

        while ((line = br.readLine()) != null) {
            System.out.println(line);
        }
    }

    public static List&lt;String&gt; getFileList(String path) throws IOException {
        return Files.list(new File(path).toPath()).map(p -&gt; p.toString()).toList();
    }

    public static List&lt;String&gt; getFileListRecursive(String path) throws IOException {
        List&lt;String&gt; files = new ArrayList&lt;&gt;();
        try (Stream&lt;Path&gt; paths = Files.walk(Paths.get(path))) {
            paths.filter(Files::isRegularFile).forEach(p -&gt; files.add(p.toString()));
        }
        return files;
    }

    public static String encodeBase64(String s) throws UnsupportedEncodingException {
        return Base64.getEncoder().encodeToString(s.getBytes(StandardCharsets.UTF_8));
    }

    public static String decodeBase64(String s) {
        return new String(Base64.getDecoder().decode(s), StandardCharsets.UTF_8);
    }

    public String encrypt(String text) throws NoSuchAlgorithmException {
        MessageDigest md = MessageDigest.getInstance(&quot;SHA-256&quot;);
        md.update(text.getBytes());

        return bytesToHex(md.digest());
    }

    private String bytesToHex(byte[] bytes) {
        StringBuilder builder = new StringBuilder();
        for (byte b : bytes) {
            builder.append(String.format(&quot;%02x&quot;, b));
        }
        return builder.toString();
    }

    public static void writeFile(String content) throws IOException {
        try (BufferedWriter writer = new BufferedWriter(new FileWriter(&quot;./OUTPUT/REPORT.TXT&quot;))) {
            writer.write(content);
        }
    }

    public static List&lt;String&gt; readFile() {
        List&lt;String&gt; content = new ArrayList&lt;&gt;();
        try (BufferedReader reader = new BufferedReader(new FileReader(&quot;./INPUT/MONITORING.TXT&quot;))) {
            while (true) {
                String line = reader.readLine();
                if (line == null) {
                    break;
                }

                content.add(line);
            }
            return content;
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }

    public static void copyFile(String src, String dst) {
        try (InputStream inputStream = new FileInputStream(src);
                OutputStream outputStream = new FileOutputStream(dst);) {
            int byteRead = -1;
            int BUF_LEN = 4096;
            byte[] bytes = new byte[BUF_LEN];
            while ((byteRead = inputStream.read(bytes)) != -1) {
                outputStream.write(bytes, 0, byteRead);
            }

        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }

    public static String getInput() throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        String line = reader.readLine();
        return line;
    }


    public static long timeDiff(String t1, String t2) throws ParseException {
        SimpleDateFormat sdf = new SimpleDateFormat(&quot;yyyy-MM-dd HH:mm:ss.SSS&quot;);

        Date d1 = sdf.parse(t1);
        Date d2 = sdf.parse(t2);

        long diff = d1.getTime() - d2.getTime();

        return diff / 1000;
    }



    public static void example_sort() {
        List&lt;String&gt; strList = new ArrayList&lt;&gt;();
        Collections.sort(strList);
        Collections.sort(strList, Collections.reverseOrder());
        Collections.sort(strList, (m1, m2) -&gt; m1.compareTo(m2));

        strList.sort((m1, m2) -&gt; m1.length() - m2.length());

        int[] ar = {};
        Arrays.sort(ar);

        List&lt;String&gt; strList2 = Arrays.asList(&quot;1&quot;, &quot;2&quot;);
        Collections.sort(strList2);
    }

    public static void example_time() throws ParseException {

        LocalDateTime now = LocalDateTime.now();
        String nowS = now.format(DateTimeFormatter.ofPattern(&quot;yyyy-MM-dd HH:mm:ss.SSS&quot;));

        long ct = System.currentTimeMillis();
        SimpleDateFormat sdf = new SimpleDateFormat(&quot;yyyy-MM-dd HH:mm:ss.SSS&quot;);
        String sdfS = sdf.format(ct);

        String sTime = &quot;2022-12-12 12:00:00&quot;;
        SimpleDateFormat format = new SimpleDateFormat(&quot;yyyy-MM-dd HH:mm:ss.SSS&quot;);
        Date dt = format.parse(sTime);

        LocalDateTime ldt = dt.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
    }

}</code></pre>
<p>ResponseBody.java</p>
<pre><code class="language-java">public class ResponseBody {
    public String result = &quot;ok&quot;;

    public ResponseBody() {

    }

    public String toString() {
        return new Gson().toJson(this);
    }

}</code></pre>
<p>ThreadInClass.java</p>
<pre><code class="language-java">public class ThreadInClass {
    public void run() throws Exception {
        Thread th = new Thread(() -&gt; {
            while(true) {
                Utils.sleep(1000);

                // todo
            }
        });

        th.start();
    }
}</code></pre>
<p>이클립스에서 input 데이터 파일 설정</p>
<p><img src="https://velog.velcdn.com/images/hungry_foolish/post/4b2a9532-4f8f-42ec-9417-a94b1f38498e/image.png" alt=""></p>
<p>자동완성 설정
<a href="https://devlimk1.tistory.com/9">https://devlimk1.tistory.com/9</a></p>
<p>이클립스 단축키</p>
<table>
<thead>
<tr>
<th>function</th>
<th>shortcut</th>
</tr>
</thead>
<tbody><tr>
<td>run</td>
<td>ctrl + F11</td>
</tr>
<tr>
<td>debug</td>
<td>F11</td>
</tr>
<tr>
<td>step info</td>
<td>F5</td>
</tr>
<tr>
<td>step over</td>
<td>F6</td>
</tr>
<tr>
<td>step return</td>
<td>F7</td>
</tr>
<tr>
<td>resume</td>
<td>F8</td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[jetty]]></title>
            <link>https://velog.io/@hungry_foolish/jetty</link>
            <guid>https://velog.io/@hungry_foolish/jetty</guid>
            <pubDate>Sat, 11 Jun 2022 10:50:30 GMT</pubDate>
            <description><![CDATA[<pre><code class="language-java">public class Main {

    static final Logger log = Logger.getGlobal();

    public static void main(String[] args) throws Exception {
        log.setLevel(Level.INFO);

        HttpServer server = new HttpServer();
        server.start();
    }
}

public class HttpServer { 
    private Server server;
    public void start() {
        server = new Server();
        ServerConnector http = new ServerConnector(server);
        http.setHost(&quot;127.0.0.1&quot;);
        http.setPort(8080);
        server.addConnector(http);

        ServletHandler servletHandler = new ServletHandler();
        servletHandler.addServletWithMapping(MyServlet.class, &quot;/*&quot;);
        server.setHandler(servletHandler);

        try {
            server.start();
            server.join();
        } catch (Exception e) {
        }
    }    

    public void stop() {
        try {
            server.stop();
        } catch (Exception e) {
        }
    }
}


public class MyServlet extends HttpServlet {

    static final Logger log = Logger.getGlobal();

    protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException {
        String path = req.getPathInfo().toString();
        String [] paths = path.split(&quot;/&quot;);
        log.info(&quot;paths: &quot; + String.join(&quot;, &quot;, paths));

        switch(paths[0]) {
            case &quot;cmd1&quot;:
                break;
            case &quot;cmd2&quot;:
                break;
            default:
                break;
        }
    }

    protected void doPost(HttpServletRequest req, HttpServletResponse res) throws IOException {
        String path = req.getPathInfo().toString();
        String [] paths = path.split(&quot;/&quot;);
        log.info(&quot;paths: &quot; + String.join(&quot;, &quot;, paths));

        switch(paths[0]) {
            case &quot;cmd1&quot;:
                Class body = getBody(req, cls);
                break;
            case &quot;cmd2&quot;:
                break;
            default:
                break;
        }
    }

    public &lt;T&gt; T getBody(HttpServletRequest req, Class&lt;T&gt; cls) throws IOException {
        return new Gson().fromJson(getBody(req), cls);
    }

    public String getBody(HttpServletRequest req) throws IOException {

        String body = null;
        StringBuilder stringBuilder = new StringBuilder();
        BufferedReader bufferedReader = null;

        try {
            InputStream inputStream = req.getInputStream();
            if (inputStream != null) {
                bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
                char[] charBuffer = new char[128];
                int bytesRead = -1;
                while ((bytesRead = bufferedReader.read(charBuffer)) &gt; 0) {
                    stringBuilder.append(charBuffer, 0, bytesRead);
                }
            }
        } catch (IOException ex) {
            throw ex;
        } finally {
            if (bufferedReader != null) {
                try {
                    bufferedReader.close();
                } catch (IOException ex) {
                    throw ex;
                }
            }
        }

        body = stringBuilder.toString();
        return body;
    }

}
</code></pre>
<p>참고: <a href="https://blog.naver.com/PostView.naver?blogId=ambidext&amp;logNo=222434508009&amp;categoryNo=0&amp;parentCategoryNo=10&amp;viewDate=&amp;currentPage=1&amp;postListTopCurrentPage=1&amp;from=search">https://blog.naver.com/PostView.naver?blogId=ambidext&amp;logNo=222434508009&amp;categoryNo=0&amp;parentCategoryNo=10&amp;viewDate=&amp;currentPage=1&amp;postListTopCurrentPage=1&amp;from=search</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[fastapi 비동기 처리 worker가 timeout으로 kill 될 때 해결방안]]></title>
            <link>https://velog.io/@hungry_foolish/gunicorn-timeout-kill-process</link>
            <guid>https://velog.io/@hungry_foolish/gunicorn-timeout-kill-process</guid>
            <pubDate>Fri, 01 Apr 2022 12:33:02 GMT</pubDate>
            <description><![CDATA[<p>Django, flask에서 비동기 처리 방법을 찾아보면 주로 celery를 활용하는 방법이 검색된다. celery를 내가 잘 몰라서 그런거겠지만 설정할 게 굉장히 많고(복잡하고) 메시지 큐(redis, rabbitmq 또는 rdb도 가능하다고는 하는데..)도 필요하다.</p>
<p>fastapi는 자체적으로 background 기능을 제공한다.
<a href="https://fastapi.tiangolo.com/tutorial/background-tasks/">https://fastapi.tiangolo.com/tutorial/background-tasks/</a></p>
<p>정말 오래걸리는 작업을 별도 프로세스/서버로 실행해야 한다면 celery가 좋은 방법이 되겠지만 나는 단순하지만 약간의 지연을 발생시키고 실시간으로 처리될 필요는 없는 그런 작업을 비동기로 처리하고자 했고 fastapi의 BackgroundTask를 최적의 선택지로 판단했다. </p>
<p>요청을 받고 오래 걸리는 작업은 background로 보내놓고 background로 넘어간 작업은 기다리지 않고 바로 응답할 수 있어서 불필요한 지연을 제거할 수 있다.</p>
<p>fastapi 거의 대부분의 기능이 그러하듯 사용법은 정말 간단하다.
path 처리 함수에 BackgroundTask를 파라미터로 정의하고 함수 안에서는 BackgroundTask에 오래 걸리는 작업을 추가(addTask)만 하면 된다.
<a href="https://fastapi.tiangolo.com/tutorial/background-tasks/#using-backgroundtasks">https://fastapi.tiangolo.com/tutorial/background-tasks/#using-backgroundtasks</a></p>
<p>처음엔 간단하게 rdb에 처리 이력을 저장하는 용도로 BackgroundTask를 사용했는데 처리 시간이 오래 걸리는 작업도 비동기로 처리할 필요가 생겼다. celery를 추가하기엔 너무 과한것 같아서 마찬가지로 BackgroundTask로 처리했다.</p>
<p>잘 실행되는 듯 했는데 gunicorn_error.log를 확인해보니 gunicorn timeout을 넘어가는 경우 해당 worker가 죽임(?)을 당했다.</p>
<pre><code class="language-bash">[2017-01-21 14:07:37 +0000] [26635] [CRITICAL] WORKER TIMEOUT (pid:27202)
[2017-01-21 14:07:37 +0000] [26635] [CRITICAL] WORKER TIMEOUT (pid:27205)</code></pre>
<p>이제와서 celery, mq를 추가 적용해야 하나 걱정하면서 열심히 찾아봤는데 아래 링크에 제시된 방법 중 3번째 <a href="https://github.com/tiangolo/fastapi/issues/1066#issuecomment-612940187"><code>fastapi.concurrency.run_in_threadpool</code></a>를 적용해서 해결했다.
<a href="https://stackoverflow.com/questions/67599119/fastapi-asynchronous-background-tasks-blocks-other-requests">https://stackoverflow.com/questions/67599119/fastapi-asynchronous-background-tasks-blocks-other-requests</a></p>
<p>위 링크에 제시된 다른 방법도 검토하고 테스트도 해봤는데.. </p>
<ol>
<li>Use more workers (e.g. uvicorn main:app --workers 4). This will allow up to 4 somelongcomputation in parallel.</li>
</ol>
<ul>
<li>worker를 늘리더라도 timeout은 피할 수 없는 구조라서 제외</li>
</ul>
<ol start="2">
<li>Rewrite your task to not be async (i.e. define it as def task(data): ... etc). Then starlette will run it in a separate thread.</li>
</ol>
<ul>
<li>path 처리 함수부터 이미 async로 정의되고 거의 대부부의 함수를 async로 정의한 상태라서 전부 다 수정해야 해서 제외</li>
</ul>
<ol start="3">
<li>Use fastapi.concurrency.run_in_threadpool, which will also run it in a separate thread</li>
</ol>
<ul>
<li>선택</li>
</ul>
<ol start="4">
<li>use asyncios&#39;s run_in_executor directly (which run_in_threadpool uses under the hood)</li>
</ol>
<ul>
<li>테스트해봤는데 문제가 해결되지 않아서 제외</li>
</ul>
<ol start="5">
<li>Spawn a separate thread / process yourself. E.g. using concurrent.futures.</li>
</ol>
<ul>
<li>테스트해봤는데 문제가 해결되지 않아서 제외</li>
</ul>
<ol start="6">
<li>Use something more heavy-handed like celery.</li>
</ol>
<ul>
<li>celery라서 제외</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[내 생애 첫 github pull request / merge]]></title>
            <link>https://velog.io/@hungry_foolish/My-first-pull-request-in-github</link>
            <guid>https://velog.io/@hungry_foolish/My-first-pull-request-in-github</guid>
            <pubDate>Fri, 01 Apr 2022 11:56:21 GMT</pubDate>
            <description><![CDATA[<p>우연히 <code>BentoML</code>라는 오픈소스를 알게 됐다. </p>
<p>얼마나 좋은 라이브러리인지 알아보고자 <a href="https://github.com/bentoml/BentoML/blob/main/README.md">readme</a>와 <a href="https://docs.bentoml.org/en/latest/quickstart.html">quickstart</a>를 열심히 읽어봤다.</p>
<p>그러다가 오타(push에 대한 설명인데 delete를 사용)를 확인했고 
내 생애 처음으로 github 오픈소스에 pull request를 해봤다.</p>
<p>그리고 다행히(?) merge까지 됐다.</p>
<p><a href="https://github.com/bentoml/BentoML/pull/2319">https://github.com/bentoml/BentoML/pull/2319</a>
<img src="https://images.velog.io/images/hungry_foolish/post/9d332a4e-80e6-4588-83e0-64d7f314ce38/image.png" alt=""></p>
<p>그러나 벌써 한달이 지나서 pull request 절차가 기억이 나질 않는다..
이래서 미리미리 정리해야 하는데.. ㅠ</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[GCP] gcr 이미지 정리]]></title>
            <link>https://velog.io/@hungry_foolish/GCP-gcr-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@hungry_foolish/GCP-gcr-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Fri, 01 Apr 2022 11:48:48 GMT</pubDate>
            <description><![CDATA[<p>gcr에 오래된 image가 계속 쌓여서 사용하지 않을 image를 일괄 삭제하는 스크립트를 정리해본다.</p>
<ul>
<li><p>gcr에 등록된 docker image 중에 tag가 지정되어 있지 않은 image 일괄 삭제 스크립트</p>
<pre><code class="language-bash">export IMG_NAME=&quot;This-is-test-image&quot;  # docker image명

gcloud container images list-tags $IMG_NAME --filter=&#39;-tags:*&#39; --format=&quot;get(digest)&quot; | xargs -I{} gcloud container images delete $IMG_NAME@&quot;{}&quot; --quiet</code></pre>
</li>
</ul>
<ul>
<li><p>앞 부분: tag가 지정되어 있지 않은(<code>--filter=&#39;-tags:*&#39;</code>) docker image명을 조회하는데 hash값만 출력(<code>--format=&quot;get(digest)&quot;</code>)</p>
<pre><code class="language-bash">export IMG_NAME=&quot;This-is-test-image&quot;  # docker image명

gcloud container images list-tags $IMG_NAME --filter=&#39;-tags:*&#39; --format=&quot;get(digest)&quot;</code></pre>
</li>
<li><p>뒷 부분: 앞에서 출력된 hash값을 <code>xargs</code>로 받아서 조용히(<code>--quiet</code>) gcr에서 삭제</p>
<pre><code class="language-bash">export IMG_NAME=&quot;This-is-test-image&quot;  # docker image명

xargs -I{} gcloud container images delete $IMG_NAME@&quot;{}&quot; --quiet</code></pre>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[tensorflow serving] 로그 레벨 설정]]></title>
            <link>https://velog.io/@hungry_foolish/tensorflow-serving-%EB%A1%9C%EA%B7%B8-%EB%A0%88%EB%B2%A8-%EC%84%A4%EC%A0%95</link>
            <guid>https://velog.io/@hungry_foolish/tensorflow-serving-%EB%A1%9C%EA%B7%B8-%EB%A0%88%EB%B2%A8-%EC%84%A4%EC%A0%95</guid>
            <pubDate>Fri, 04 Feb 2022 03:09:15 GMT</pubDate>
            <description><![CDATA[<p>tensorflow serving 도커 컨테이너로 실행하는데 로그가 너무 많이 출력되고 있다.
아무런 처리를 안해도 거의 매초 info 레벨 로그가 찍히는 듯한 느낌.</p>
<p>그래서 로그 레벨을 warn 이상으로 변경을 알아봤다.</p>
<p>그렇게 찾은 아래 링크에서 TF_CPP_MIN_VLOG_LEVEL을 설정하라고 하는데 효과가 없었다.
<a href="https://github.com/tensorflow/serving/issues/285">https://github.com/tensorflow/serving/issues/285</a></p>
<p>정답은 TF_CPP_MIN_LOG_LEVEL</p>
<pre><code class="language-bash">export TF_CPP_MIN_LOG_LEVEL=1</code></pre>
<p>설정 후에 다시 실행하니 warning 레벨 이상 로그만 출력되는 것을 확인..</p>
<p>참고로 TF_CPP_MIN_LOG_LEVEL 설정값은 아래와 같다.</p>
<pre><code>0: debug
1: warn
2: error
3: disable</code></pre><p>(info가 안보인다?... 이건 나중에 알아보기로... )</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Springboot] Parameter 1 of constructor in ClassA required a bean of type 'ClassB' that could not be found]]></title>
            <link>https://velog.io/@hungry_foolish/Springboot-Parameter-1-of-constructor-in-ClassA-required-a-bean-of-type-ClassB-that-could-not-be-found</link>
            <guid>https://velog.io/@hungry_foolish/Springboot-Parameter-1-of-constructor-in-ClassA-required-a-bean-of-type-ClassB-that-could-not-be-found</guid>
            <pubDate>Thu, 03 Feb 2022 13:55:25 GMT</pubDate>
            <description><![CDATA[<p>jwt 적용 연습 중에 맞이한 에러를 정리한다.</p>
<pre><code class="language-java">@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    private final TokenProvider tokenProvider;
    private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
    private final JwtAccessDeniedHandler jwtAccessDeniedHandler;

    ...
}

public class JwtAccessDeniedHandler implements AccessDeniedHandler {
    ...
}

public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
    ...
}</code></pre>
<p>에러 메시지는 아래와 같다.
Parameter 1 of constructor in ClassA required a bean of type &#39;ClassB&#39; that could not be found</p>
<p>단순한건데.. 오랜만에 보니 한참을 헤맸다.
JwtAccessDeniedHandler, JwtAuthenticationEntryPoint 정의부 상단에 <code>@Component</code>를 추가하니 해결~</p>
<pre><code class="language-java">@Component
public class JwtAccessDeniedHandler implements AccessDeniedHandler {
    ...
}

@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
    ...
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Springboot] 테스트용 ssl 적용하기]]></title>
            <link>https://velog.io/@hungry_foolish/Springboot-localhost%EC%97%90-ssl-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@hungry_foolish/Springboot-localhost%EC%97%90-ssl-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Thu, 03 Feb 2022 11:07:24 GMT</pubDate>
            <description><![CDATA[<p>https로 Springboot 서버를 어떻게 실행할 수 있을지 궁금했었는데 방법을 찾아서 정리해본다.</p>
<p>application.yml에는 아래와 같은 ssl 설정을 추가한다.</p>
<pre><code class="language-yaml">server:
  ssl:
    enabled: true
    key-store: keystore.p12
    key-store-password: 1q2w3e4r
    key-store-type: PKCS12
    key-alias: bns-ssl
  port: 8443</code></pre>
<p>위에서 정한 값을 keytool 인자로 전달하여 인증서 파일을 생성한다.
(keytool은 jdk에 포함됨)</p>
<pre><code class="language-bash"># linux
# keytool -genkey -alias ${key-alias} -storetype ${key-store-type} -keyalg RSA -keysize 2048 -keystore ${key-store} -validity 3650

# windows: java_home 설정 필요 
# keytool.exe -genkey -alias ${key-alias} -storetype ${key-store-type} -keyalg RSA -keysize 2048 -keystore ${key-store} -validity 3650

keytool -genkey -alias bns-ssl -storetype PKCS12 -keyalg RSA -keysize 2048 -keystore keystore.p12 -validity 3650</code></pre>
<p>생성된 인증서 파일 keystore.p12을 springboot 프로젝트 root 경로로 이동한다.
<img src="https://images.velog.io/images/hungry_foolish/post/90953a74-bbd2-465a-b068-50fcd7ed8211/image.png" alt=""></p>
<p>서버를 실행하고 브라우저에서 <a href="https://localhost:8443%EC%9C%BC%EB%A1%9C">https://localhost:8443으로</a> 접속해서 확인한다.</p>
<p>참고: <a href="https://jojoldu.tistory.com/350?category=635883">https://jojoldu.tistory.com/350?category=635883</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Springboot] PostConstruct에서 transactional 처리]]></title>
            <link>https://velog.io/@hungry_foolish/Spring-PostConstruct%EC%97%90%EC%84%9C-transactional-%EC%B2%98%EB%A6%AC</link>
            <guid>https://velog.io/@hungry_foolish/Spring-PostConstruct%EC%97%90%EC%84%9C-transactional-%EC%B2%98%EB%A6%AC</guid>
            <pubDate>Wed, 02 Feb 2022 12:39:54 GMT</pubDate>
            <description><![CDATA[<p>RestController 클래스 생성 후 테스트용 데이터를 초기화하는 아래 코드를 추가했다.</p>
<pre><code class="language-java">    @PostConstruct
    public void initData() {
        Team teamA = new Team(&quot;teamA&quot;);
        Team teamB = new Team(&quot;teamB&quot;);

        for (int i = 0; i &lt; 100; i++) {
            Team selectedTeam = i % 2 == 0 ? teamA : teamB;
            em.persist(new Member(&quot;member&quot; + i, selectedTeam, i));
        }
    }
</code></pre>
<p>그리고 맞이한 에러 메시지...</p>
<p>Caused by: javax.persistence.TransactionRequiredException: No EntityManager with actual transaction available for current thread - cannot reliably process &#39;persist&#39; call</p>
<p>그래서 찾아본 결과 아래와 같은 해결방법을 찾았다.
<a href="https://sorjfkrh5078.tistory.com/311">https://sorjfkrh5078.tistory.com/311</a></p>
<ul>
<li>다른 스프링 빈을 호출해서 사용하는 방법</li>
<li>AOP를 사용하지 않고 트랜잭션을 직접 코딩하는 방법</li>
<li>애플리케이션 컨텍스트가 완전히 초기화된 이벤트를 받아서 호출하는 방법</li>
<li>초기화하는 메서드와 초기화를 실행하는 메서드를 분리하는 방법</li>
</ul>
<p>내가 적용한 방법은 4번째. 다른 스프링 빈을 호출해서 사용하는 방법.</p>
<p>아래와 같은 InitMemberService를 추가하고,</p>
<pre><code class="language-java">@Component
public class InitMemberService {

    @PersistenceContext
    EntityManager em;

    @Transactional
    public void init() {
        Team teamA = new Team(&quot;teamA&quot;);
        Team teamB = new Team(&quot;teamB&quot;);

        em.persist(teamA);
        em.persist(teamB);

        for (int i = 0; i &lt; 100; i++) {
            Team selectedTeam = i % 2 == 0 ? teamA : teamB;
            em.persist(new Member(&quot;member&quot; + i, selectedTeam, i));
        }
    }
}</code></pre>
<p>RestController 클래스에서 주입받고 실행하니 정상적으로 처리됐다.</p>
<pre><code class="language-java">@RestController
public class MemberController {

    @Autowired
    private InitMemberService initMemberService;

    @PostConstruct
    public void initData() {
        initMemberService.init();
    }
</code></pre>
<p>참조</p>
<ul>
<li><a href="https://sorjfkrh5078.tistory.com/311">https://sorjfkrh5078.tistory.com/311</a></li>
<li><a href="https://www.inflearn.com/questions/270247">https://www.inflearn.com/questions/270247</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[FastAPI] reponse["content-disposition"] === undefined ???]]></title>
            <link>https://velog.io/@hungry_foolish/FastAPI-reponsecontent-disposition-undefined</link>
            <guid>https://velog.io/@hungry_foolish/FastAPI-reponsecontent-disposition-undefined</guid>
            <pubDate>Sat, 29 Jan 2022 05:43:47 GMT</pubDate>
            <description><![CDATA[<p>fastapi에서 파일이나 stream을 응답할 때 FileResponse를 구성해서 응답한다. 
파일명을 포함하는 방법은 아래와 같이 filename 인자를 추가해주면 응답 헤더 content-disposition에 filename이 추가된다.</p>
<pre><code class="language-python">## https://fastapi.tiangolo.com/ko/advanced/custom-response/?h=#fileresponse

import uvicorn as uvicorn
from fastapi import FastAPI
from fastapi.responses import FileResponse

some_file_path = &quot;pyproject.toml&quot;
app = FastAPI()


@app.get(&quot;/&quot;)
async def main():
    return FileResponse(some_file_path, filename=some_file_path)


if __name__ == &quot;__main__&quot;:
    uvicorn.run(&quot;main:app&quot;, reload=True)</code></pre>
<p>브라우저 개발자 화면에서 보이는 Reponse Headers에 포함된 content-disposition
<img src="https://images.velog.io/images/hungry_foolish/post/f407c6db-6f97-4ef7-8a18-90a2b4fb9178/image.png" alt="브라우저 개발자 화면에서 보이는 Reponse Headers에 포함된 content-disposition"></p>
<p>하지만 javascript에서 읽으려고 하니 undefined가 나온다.</p>
<p>뭐가 문젤까..</p>
<p>검색을 해보니 백엔드의 cors 설정 문제이고 expose header 설정이 필요하다고 하여 fastapi 공식 가이드에 나온데로 아래와 같이 설정해봤지만 결과는 동일했다.</p>
<pre><code class="language-python">## https://fastapi.tiangolo.com/tutorial/cors/

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()

origins = [
    &quot;http://localhost.tiangolo.com&quot;,
    &quot;https://localhost.tiangolo.com&quot;,
    &quot;http://localhost&quot;,
    &quot;http://localhost:8080&quot;,
]

app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_credentials=True,
    allow_methods=[&quot;*&quot;],
    allow_headers=[&quot;*&quot;],
)


@app.get(&quot;/&quot;)
async def main():
    return {&quot;message&quot;: &quot;Hello World&quot;}
</code></pre>
<p>고민하던 차에 CORSMiddleware 객체 정의부 소스를 살펴보니 </p>
<pre><code class="language-python">class CORSMiddleware:
    def __init__(
        self,
        app: ASGIApp,
        allow_origins: typing.Sequence[str] = (),
        allow_methods: typing.Sequence[str] = (&quot;GET&quot;,),
        allow_headers: typing.Sequence[str] = (),
        allow_credentials: bool = False,
        allow_origin_regex: str = None,
        expose_headers: typing.Sequence[str] = (),
        max_age: int = 600,
    ) -&gt; None:</code></pre>
<p>여기에 expose_headers 옵션이 있었구나!</p>
<p>그래서 아래와 같이 옵션을 추가하니 해결~~</p>
<pre><code class="language-python">app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_credentials=True,
    allow_methods=[&quot;*&quot;],
    allow_headers=[&quot;*&quot;],
    expose_headers=[&quot;*&quot;],
)</code></pre>
<p><img src="https://images.velog.io/images/hungry_foolish/post/5f7e60dd-bc71-4cb0-a2a7-40002a3868dd/image.png" alt=""></p>
<p>오늘의 교훈.. </p>
<p>프레임워크의 기능을 구글링해서 못 찾겠으면 정의된 코드를 찾아보자!</p>
<p>다 적고 보니 아래 링크에 잘 설명되어 있다..
<a href="https://fastapi.tiangolo.com/ko/tutorial/cors/?h=">https://fastapi.tiangolo.com/ko/tutorial/cors/?h=</a></p>
<p>다시 적어보는 오늘의 교훈</p>
<h1 id="우선-tutorial을-잘-읽어볼것">우선 tutorial을 잘 읽어볼것!!</h1>
]]></description>
        </item>
    </channel>
</rss>