멀티캠퍼스 백엔드 과정41일차[8월 1일] - 네이버 검색 api, ajax, json

GoldenDusk·2023년 8월 1일
0

개념 정리

소켓(socket)이란?

소켓(socket)

  • 네트워크에서 동작하는 프로그램의 종착점(endpoint)
  • IP와 포트번호로 이루어져 있다.
serverSocket = new ServerSocket(50002);
  • 서버와 클라이언트의 사이에서 양방향 통신을 할 수 있도록 해주는 소프트웨어 장치
  • 양방향으로 통신을 하려면 서로를 알아야 하므로 클라이언트와 서버 둘 다 소켓을 생성하여 연결해 주는 것
    • chatClient

      socket = new Socket("localhost", 50002);
    • chatServer

      serverSocket = new ServerSocket(50002);
  • 자바는 소켓기능을 클래스로 제공해 준다.
    • 서버 소켓 기능/소켓 기능을 구분하여 절차적 정리 후 동작원리 완벽히 숙지 해야 한다.

웹 소켓 프로그래밍

1. 자주 쓰는 tomcat 버전

웹 소켓 1.1

동기와 비동기

놀러와요, 비동기의 숲

2. web소켓이란?

webSocket

  • WebSocket은 브라우저에서 지원하는 API의 한 종류
  • TCP/IP, HTTP,SMTP 와 같이 WebSocket을 사용하기 위한 프로토콜이 ws프로토콜이며, 비동기적으로 클라이언트와 서버 사이에 독립적으로 지속적인 양방향 연결(Stream)을 해주는 기술
  • 실시간 통신이 가능
  • 공부 사이트 : **웹 소켓에 대해 알아보자!**

웹 소켓에 대해 알아보자! - 이론 편

웹 소켓에 대해 알아보자! - 실전 편

동작방식

  • 연속적인 데이터 전송의 신뢰성을 보장하기 위해 HTTP 요청 기반으로 Handshack 과정 진행하여 연결을 수립
  • 연결을 수립하기 위해 upgrade 헤더와 connection 헤더를 포함하는 HTTP요청을 보낸다.

웹 소켓 VS HTTP

  • 웹 소켓이 HTTP요청으로 시작되며 HTTP에서 동작하지만, 두 프로토콜은 분명히 다르게 동작
  • HTTP
    • 클라이언트와 서버 간 접속을 유지하지 않으면, 요청과응답 형태의 단방향 통신만 가능
    • 서버에서 클라이언트로 요청은 불가능
    • 요청-응답이 완료되면 수립했던 연결이 닫힌다.
    • REST한 방식의 HTTP 통신에서는 많은 URI와 Http Method를 통해 웹 어플리케이션과 상호작용
  • 웹 소켓
    • 클라이언트와 서버 간 접속이 유지되면 요청과 응답 개념이 아닌 서로 데이터를 주고 받는 형식
    • 초기 연결 수립을 위한 오직 하나의 URL만 존재하며, 모든 메세지는 초기에 연결된 TCP 연결로만 통신

3. 채팅 서버 구현

  • 새로 접속한 클라이언트의 세션을 저장한 컬렉션을 생성. Collections 클래스의 synchronizedSet( ) 메서드는 멀티 스레드 환경에서도 안전한(thread-safe) Set 컬렉션을 생성
    • 즉, 여러 클라이언트가 동시에 접속해도 문제가 생기지 않도록 안전하게 동기화
  • 접속한 클라이언트들을 컬렉션 관리하면서 한 클라이언트가 메세지를 보내면 다른 클라이언트로 전달하는 간단한 서버

JAVA

package websocket;

import java.io.IOException;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;

@ServerEndpoint("/ChatingServer") //1. 서버를 만들어 웹 소켓임을 지정
public class ChatServer {
	// 2. 여러 클라이언트가 동시에 접속해도 문제가 생기지 않도록 동기화
    private static Set<Session> clients
            = Collections.synchronizedSet(new HashSet<Session>());

    @OnOpen // 3. 클라이언트가 접속했을 때 실행할 메서드를 정의
    public void onOpen(Session session) {
    //  clients 컬렉션에 클라이언트의 세션을 추가
        clients.add(session); 
        System.out.println("웹소켓 연결:" + session.getId());
    }

    @OnMessage  // 메시지를 받으면 실행
    public void onMessage(String message, Session session) throws IOException {
        System.out.println("메시지 전송 : " + session.getId() + ":" + message);
        
      //5. 동시에 여러명[클라이언트들]에게 메세지를 전송해야 함
        synchronized (clients) {
            for (Session client : clients) {  // 모든 클라이언트에 메시지 전달
            	// sendText안에 금요일에 작업한 outputStream, inputStream 등이 다 들어 강 있음
				//6. 메세지를 다수에게 돌리려면 세션에서 하나씩 뽑아서 돌림
                if (!client.equals(session)) {  //7. set은 순서가 없기에 또 들어올 수 있으니 메세지 보낸 클라이언트는 제외
                    client.getBasicRemote().sendText(message);
                }
            }
        }
    }
    // 8. 클라이언트가 접속을 종료했을 때 실행할 메서드를 정의 clients에서 해당 클라이언트의 세션을 삭제
    @OnClose
    public void onClose(Session session) {
        clients.remove(session); 
        System.out.println("웹소켓 종료 : " + session.getId());
    }
    
 // 9,  에러가 발생했을 때 실행할 메서드를 정의
    @OnError  
    public void onError(Throwable e) {
        System.out.println("에러 발생");
        e.printStackTrace();
    }
}
  • Web.xml 만들기

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" id="WebApp_ID" version="4.0">
  <display-name>MutiBackend14</display-name>
  
  <context-param>
    <param-name>CHAT_ADDR</param-name>
    <param-value>ws://localhost:8090/webChatting</param-value>
  </context-param> 
</web-app>

jsp

  • chatWindow.jsp : 채팅창
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>    
<html>
<head>
<title>웹소켓 채팅</title>
<script>
/* 1. 선언해둔 웹소켓 접속 URL 뒤에 요청명을 덧붙여 웹 소켓 생성 */
var webSocket
    = new WebSocket("<%= application.getInitParameter("CHAT_ADDR") %>/ChatingServer");
/* 밑에 선언해둔 것들을 불러옴 */
var chatWindow, chatMessage, chatId;

/* 2. 채팅창이 열리면 대화창, 메세지 입력창, 대화명 표시란으로 사용할 DOM객체[body안에 element 값을 가져옴] 저장 */
window.onload = function() {
    chatWindow = document.getElementById("chatWindow");
    chatMessage = document.getElementById("chatMessage");
    chatId = document.getElementById('chatId').value;    
}

/* 3. 메세지 전송 기능 */
/* <button id="sendBtn">에 있는 이름 그대로*/
function sendMessage() {
    // 대화창에 표시
    chatWindow.innerHTML += "<div class='myMsg'>" + chatMessage.value + "</div>"
    webSocket.send(chatId + '|' + chatMessage.value);  // 서버로 전송
    chatMessage.value = "";  // 메시지 입력창 내용 지우기
    chatWindow.scrollTop = chatWindow.scrollHeight;  // 대화창 스크롤
}

/* 4. 서버와의 연결 종료 */
function disconnect() {
    webSocket.close();
}

/* 5. 엔터 키 입력처리 enterKey() 엔터키를 눌리면 sendMessage() 호출 매번[전송]버튼을 누리지 않아도 엔터를 누르면 즉시 */
function enterKey() {
    if (window.event.keyCode == 13) {  // 13은 'Enter' 키의 코드값
        sendMessage();
    }
}

/* 6. 웹소켓 서버에 연결됐을 때 실행 */
webSocket.onopen = function(event) {   
    chatWindow.innerHTML += "웹소켓 서버에 연결되었습니다.<br/>";
};

/* 7. 웹 소켓이 닫혔을 때(서버와의 연결이 끊어졌을 때) 실행 */
webSocket.onclose = function(event) {
    chatWindow.innerHTML += "웹소켓 서버가 종료되었습니다.<br/>";
};

/* 8. 에러 발생 시 실행 */
webSocket.onerror = function(event) { 
    alert(event.data);
    chatWindow.innerHTML += "채팅 중 에러가 발생하였습니다.<br/>";
}; 

/* 9. 메세지를 받았을 때 실행 */
webSocket.onmessage = function(event) { 
    var message = event.data.split("|");  // 대화명과 메시지 분리
    var sender = message[0];   // 보낸 사람의 대화명
    var content = message[1];  // 메시지 내용
    if (content != "") {
        if (content.match("/")) {  // 귓속말
            if (content.match(("/" + chatId))) {  // 나에게 보낸 메시지만 출력
                var temp = content.replace(("/" + chatId), "[귓속말] : ");
                chatWindow.innerHTML += "<div>" + sender + "" + temp + "</div>";
            }
        }
        else {  // 일반 대화
            chatWindow.innerHTML += "<div>" + sender + " : " + content + "</div>";
        }
    }
    chatWindow.scrollTop = chatWindow.scrollHeight; 
};
</script>
<style>  <!-- 대화창 스타일 지정 -->  
#chatWindow{border:1px solid black; width:270px; height:310px; overflow:scroll; padding:5px;}
#chatMessage{width:236px; height:30px;}
#sendBtn{height:30px; position:relative; top:2px; left:-2px;}
#closeBtn{margin-bottom:3px; position:relative; top:2px; left:-2px;}
#chatId{width:158px; height:24px; border:1px solid #AAAAAA; background-color:#EEEEEE;}
.myMsg{text-align:right;}
</style>
</head>

<body>  <!-- 대화창 UI 구조 정의 --> 
    대화명 : <input type="text" id="chatId" value="${ param.chatId }" readonly />
    <button id="closeBtn" onclick="disconnect();">채팅 종료</button>
    <div id="chatWindow"></div>
    <div>
        <input type="text" id="chatMessage" onkeyup="enterKey();">
        <button id="sendBtn" onclick="sendMessage();">전송</button>
    </div>    
</body>
</html>
  • MultiChatMain.jsp
  1. 채팅 참여 화면
  2. 채팅창을 팝업창으로 열어주는 화면
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<html>
<head><title>웹소켓 채팅</title></head>
<body>
    <script>
    function chatWinOpen() {
        var id = document.getElementById("chatId");
        if (id.value == "") {
            alert("대화명을 입력 후 채팅창을 열어주세요.");
            id.focus();
            return;
        }
        window.open("ChatWindow.jsp?chatId=" + id.value, "", "width=320,height=400");
        id.value = "";
    }
    </script>
    <h2>웹소켓 채팅 - 대화명 적용해서 채팅창 띄워주기</h2>
    대화명 : <input type="text" id="chatId" />
    <button onclick="chatWinOpen();">채팅 참여</button>
</body>
</html>

오늘의 궁금증

Q. 자바스크립트로 작성한 코드와 자바에 작성하는 코드는 뭐가 달라? 둘 다 동적인데 분리해서 쓰는 이유가 뭐야?

자바스크립트로 작성한 코드와 자바로 작성하는 코드는 목적과 환경에 따라 사용되는 언어가 다르다.

  1. 언어적 차이:
    • 자바스크립트: 자바스크립트는 클라이언트 측에서 웹 브라우저 내에서 실행되는 스크립트 언어입니다. 주로 HTML과 함께 사용되며, 웹 페이지의 동적인 동작, 사용자 상호작용, 데이터 검증 등을 처리하는데 사용됩니다.
    • 자바: 자바는 주로 서버 측에서 실행되는 객체 지향 프로그래밍 언어입니다. 웹 서버, 애플리케이션 서버, 모바일 앱, 소프트웨어 개발 등 다양한 분야에서 활용됩니다. 자바는 클라이언트 측에도 사용할 수 있지만, 주로 서버 측에서 사용되는 경우가 많습니다.
  2. 역할과 책임의 분리:
    • 자바스크립트: 자바스크립트는 주로 클라이언트 측에서 사용자 인터페이스와 상호작용을 담당합니다. 예를 들어, 웹 페이지의 동적인 내용 변경, 사용자 입력의 유효성 검사, 애니메이션, 이벤트 처리 등을 수행합니다.
    • 자바: 자바는 주로 서버 측에서 비즈니스 로직과 데이터 처리를 담당합니다. 데이터베이스와의 상호작용, 계산, 데이터 처리, 비즈니스 규칙 구현 등을 주로 처리합니다.
  • 관심사 분리: 자바스크립트는 클라이언트 측에서 주로 사용자 인터페이스와 상호작용을 다루는데 적합하고, 자바는 서버 측에서 비즈니스 로직과 데이터 처리에 적합합니다. 각 언어가 자신의 역할을 수행하면 코드가 더욱 읽기 쉽고 유지 보수하기 쉬워집니다. 또한, 클라이언트와 서버가 분리되어 있기 때문에 웹 애플리케이션의 확장성과 유연성이 증가합니다.
  • 보안성 측면: 자바스크립트는 클라이언트 측에서 실행되기 때문에 코드가 노출될 수 있고, 악의적인 사용자에 의해 변조될 수 있습니다. 반면 자바는 서버 측에서 실행되기 때문에 보안 측면에서 더욱 안전합니다. 중요한 비즈니스 로직과 데이터 처리는 서버 측에서 보호되므로 보안성이 높아집니다.

따라서, 자바스크립트와 자바를 각각 적절한 영역에서 사용하면서 역할과 책임을 분리하여 웹 애플리케이션을 구성하는 것이 좋습니다. 이렇게 하면 코드의 유지 보수성과 보안성을 향상시킬 수 있습니다.

Q . getBasicRemote()는 왜 사용하는 걸까요?

if(!client.equals(session)) { //7. set은 순서가 없기에 또 들어올 수 있으니 메세지 보낸 클라이언트는 제외			
client.getBasicRemote().sendText(message);}

client를 서버와 연결하여 메시지를 보내는 기능. getBasicRemote()메세지는 text,binary,pong 3가지 타입을 보낼수 있다.

오픈 API

1. 오픈 API

  • 서비스를 제공하는 업체에서 외부 개발자가 자사 서비스의 기능을 간단히 호출해
    이용할 수 있도록 공개해둔 API
  • 자사의 서비스를 다른 업체의 앱이나 서비스에서도 제공하게 하여 사용자를 늘리고 영향력을
    확대하는 것이 주된 목적
  • 주로 HTTP 프로토콜로 통신하며, 응답 데이터의 형태는 JSON 포맷을 가장 많이 사용

2. JSON 기초

객체는 키(KEY)와 값(VALUE)으로 구분

{ ←객체(중괄호)
 "name": "낙자쌤", "age":45, "address": "서울시 금천구 가산동"
}

배열은 값만으로 구성

[ ←배열(대괄호)
 "Java", "Oracle", "HTML5", "JSP", "JavaScript"
]

객체의 값으로 배열을 사용

{ ←객체
 "firstName": "Mush", "lastName": "Have",
 "Books": ["JAVA","JSP","GO","Python"] ←배열
}

배열은 값만으로 구성

[ ←배열
 {"City": "서울", "HotPlace": "이태원"}, ←객체
 {"City": "부산", "HotPlace": "해운대"},
 {"City": "대구", "HotPlace": "삼덕동"}
]

3. 오픈 API 이용

네이버 개발자 센터

NAVER Developers

⇒ API 신청

잘 기억해두기

Untitled

4. Ajax(Asynchronous JavaScript and XML)

AJAX란

  • 빠르게 동작하는 동적인 웹페이지를 만들기 위한 개발 기법
  • 웹 페이지를 전체 로딩하지 않고 웹 페이지의 일부분만 갱신할 수 있다.
  • Ajax를 이용하면 백그라운드 영역에서 서버와 통신하여 그 결과를 웹 페이지의 일부에만 표시할 수 있다.
    • JSON
    • XML
    • HTML
    • 텍스트 파일

장점

  1. 웹페이지 전체를 로딩하지 않고 부분만 갱신 가능
  2. 바이너리 데이터를 보내거나 받지 못한다.
  3. 서버로부터 데이터를 바로 받을 수 있다.
  4. 백그라운드 영역에서 서버로 데이터를 보내고 받을 수 있다.

클라이언트 풀링(client Pooling)

  • 사용자가 직접 원하는 정보를 서버에 요청하여 얻는 방식

서버 푸시(server push)

  • 사용자가 요청하지 않아도 서버가 알아서 자동으로 특정 정보를 제공
package api;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet("/NaverSearchAPI.do")
public class SearchAPI extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        // 1. 인증 정보 설정
        String clientId = "D8KD695q70780YufsUVl";
        String clientSecret = "JgTlht42uX";

        // 2. 검색 조건 설정
        int startNum = 0;    // 검색 시작 위치
        String text = null;  // 검색어
        try {
        	startNum = Integer.parseInt(req.getParameter("startNum"));
             String searchText = req.getParameter("keyword");
             text = URLEncoder.encode(searchText, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("검색어 인코딩 실패", e);
        } 

        // 3. API URL 조합
        String apiURL = "https://openapi.naver.com/v1/search/blog?query=" + text
                        + "&display=10&startNum=" + startNum;  // json 결과
        //String apiURL = "https://openapi.naver.com/v1/search/blog.xml?query=" + text;  // xml 결과

        // 4. API 호출
        Map<String, String> requestHeaders = new HashMap<>();
        requestHeaders.put("X-Naver-Client-Id", clientId);
        requestHeaders.put("X-Naver-Client-Secret", clientSecret);
        String responseBody = get(apiURL, requestHeaders);

        // 5. 결과 출력
        System.out.println(responseBody);  // 콘솔에 출력

        resp.setContentType("text/html; charset=utf-8");
        resp.getWriter().write(responseBody);  // 서블릿에서 즉시 출력
    }

    private static String get(String apiUrl, Map<String, String> requestHeaders){
        HttpURLConnection con = connect(apiUrl);
        try {
            con.setRequestMethod("GET");
            for(Map.Entry<String, String> header :requestHeaders.entrySet()) {
                con.setRequestProperty(header.getKey(), header.getValue());
            }

            int responseCode = con.getResponseCode();
            if (responseCode == HttpURLConnection.HTTP_OK) { // 정상 호출
                return readBody(con.getInputStream());
            } else { // 에러 발생
                return readBody(con.getErrorStream());
            }
        } catch (IOException e) {
            throw new RuntimeException("API 요청과 응답 실패", e);
        } finally {
            con.disconnect();
        }
    }

    private static HttpURLConnection connect(String apiUrl){
        try {
            URL url = new URL(apiUrl);
            return (HttpURLConnection)url.openConnection();
        } catch (MalformedURLException e) {
            throw new RuntimeException("API URL이 잘못되었습니다. : " + apiUrl, e);
        } catch (IOException e) {
            throw new RuntimeException("연결이 실패했습니다. : " + apiUrl, e);
        }
    }

    private static String readBody(InputStream body){
        InputStreamReader streamReader = new InputStreamReader(body);

        try (BufferedReader lineReader = new BufferedReader(streamReader)) {
            StringBuilder responseBody = new StringBuilder();

            String line;
            while ((line = lineReader.readLine()) != null) {
                responseBody.append(line);
            }

            return responseBody.toString();
        } catch (IOException e) {
            throw new RuntimeException("API 응답을 읽는데 실패했습니다.", e);
        }
    }
}

회고

서버 문제인지 자꾸 nullpointException이 난다... 강사님도 처음에는 오타문제인것 같다고 하셨는데 확인해보시더니 서버 문제인 것 같다고 지금까지도 찾아보고 있는데 안돼서 이클립스 다시 깔고 서버 구축 다시 하는 중이다...

profile
내 지식을 기록하여, 다른 사람들과 공유하여 함께 발전하는 사람이 되고 싶다. 참고로 워드프레스는 아직 수정중

2개의 댓글

comment-user-thumbnail
2023년 8월 1일

좋은 정보 감사합니다

1개의 답글