Developer Document

Embedded Software 개발을 하면서 습득한 내용을 적은 시니어 개발자들의 글

코 독 코 독 CoderDocument

Embedded SW 기초/네트워크

HTTP

뜨요르 2024. 6. 4. 23:13

HTTP(Hypertet Transfer Protocol)는 웹에서 데이터를 주고받는 서버-클라이언트 모델의 프로토콜이다.

클라이언트의 요청과 서버의 응답 사이에는 여러 프록시 서버가 있다. 이 모든 통신은 안전하게 이뤄지기 위해 TCP연결을 사용한다. Http Header를 보면 많은 정보들이 담겨있고 그 정보들은 key와 value로 존재한다.

 

NAVER F12키 누른 후 HTTP Header 정보

 

Http Header란?

클라이언트와 서버가 요청 또는 응답으로 부가적인 정보를 전송하는 데이터이다. 헤더는 크게 4가지로 분류할 수 있다.

  • General Header (공통 헤더)
  • Request Header (요청 헤더)
  • Response Header (응답 헤더)
  • Entity Header (엔티티 헤더)

General Header (공통 헤더)

요청과 응답 모두에 적용되지만 바디에서 최종적으로 전송되는 데이터와는 관련이 없는 헤더

  • Date : 현재시간
  • Pragma : 캐시제어, HTTP/1.0에서 쓰던 것으로 HTTP/1.1에서는 Cache-Contol이 쓰임
  • Cache-Contol : 캐시를 제어할 때 사용
  • Upgrde : 프로토콜 변경 시 사용
  • Via : 프록시 서버의 이름, 버전, 호스트명
  • Connection : 네트워크 접속을 유지할지 말지 제어. HTTP/1.1은 kepp-alive로 연결
  • Transfer-Encoding : 사용자에게 entity를 안전하게 전송하기 위해 사용하는 인코딩 형식을 지정

Entity Header (엔티티 헤더)

콘텐츠 길이나 MIME 타입과 같이 엔티티 바디에 대한 자세한 정보를 포함하는 헤더

  • Content-type : 리소스의 media type 명시 ex) application/json, text/html
  • Content-Length : 바이트 단위를 가지는 개체 본문의 크기
  • Content-language : 본문을 이해하는데 가장 적절한 언어
  • Content-location : 반환된 데이터 개체의 실제 위치 ex) /index.html
  • Content-disposition : 응답 메시지를 브라우저가 어떻게 처리할지. 주로 다운로드에 사용
  • Content-Security-Policy : 다른 외부 파일을 불러오는 경우 차단할 리소스와 불러올 리소스 명시
    • ex) default-src https -> https로만 파일을 가져옴
    • ex) default-src 'self' -> 자기 도메인에서만 가져옴
    • ex) default-src 'none' -> 외부파일은 가져올 수 없음
  • Content-Encoding : 본문의 리소스 압축 방식. 주로 Content-Type과 같이 사용되며 참조되는 미디어 타입을 얻도록 디코드 하는 방법을 클라이언트가 알게 해 줍니다.
  • Location : 301, 302 상태코드일 때만 볼 수 있는 헤더로 서버의 응답이 다른 곳에 있다고 알려주면서 해당 위치(URI)를 지정
  • Last-Modified : 리소스의 마지막 수정 날짜
  • Allow : 지원되는 HTTP 요청 메서드 ex) GET, HEAD, POST
  • Expires : 자원의 만료 일자
  • ETag : 리소스의 버전을 식별하는 고유한 문자열 검사기(주로 캐시 확인용으로 사용)

Request Header (요청 헤더)

 HTTP 요청에서 사용되지만 메시지의 콘텐츠와 관련이 없는 패치될 리소스나 클라이언트 자체에 대한 자세한 정보를 포함하는 헤더

  • Host : 요청하려는 서버 호스트 이름과 포트번호
  • User-agent : 클라이언트 프로그램 정보 ex) Mozilla/4.0, Windows NT5.1
  • Referer : 현재 페이지로 연결되는 링크가 있던 이전 웹 페이지의 주소
  • Accept : 클라이언트가 처리 가능한 MIME Type 종류 나열
  • Accept-charset : 클라이언트가 지원가능한 문자열 인코딩 방식
  • Accept-language : 클라이언트가 지원가능한 언어 나열
  • Accept-encoding : 클라이언트가 해석가능한 압축 방식 지정
  • If-Modified-Since : 여기에 쓰인 시간 이후로 변경된 리소스 취득. 캐시가 만료되었을 때에만 데이터를 전송하는 데 사용
  • Authorization : 인증 토큰을 서버로 보낼 때 쓰이는 헤더
  • Origin : 서버로 Post 요청을 보낼 때 요청이 어느 주소에서 시작되었는지 나타내는 값. 경로 정보는 포함하지 않고 서버 이름만 포함
    • 이 값으로 요청을 보낸 주소와 받는 주소가 다르면 CORS 에러가 난다.
  • Cookie : 쿠기 값 key-value로 표현된다. Set-Cookie 헤더와 함께 서버로부터 이전에 전송됐던 저장된 HTTP 쿠키를 포함

Response Header (응답 헤더)

 위치 또는 서버 자체에 대한 정보(이름, 버전)과 같이 응답에 대한 부가적인 정보를 갖는 헤더

  • Server : 웹서버의 종류
  • Age : max-age 시간 내에서 얼마나 흘렀는지 초 단위로 알려주는 값
  • Referrer-policy : 서버 referrer 정책을 알려주는 값 ex) origin, no-referrer, unsafe-url
  • WWW-Authenticate : 사용자 인증이 필요한 자원을 요구할 시, 서버가 제공하는 인증 방식
  • Proxy-Authenticate : 요청한 서버가 프록시 서버인 경우 유저 인증을 위한 값
  • Set-Cookie : 서버 측에서 클라이언트에게 세션 쿠키 정보를 설정 (RFC 2965에서 규정)

HTTP Client 구현

위에서 말했듯 HTTP는 TCP프로토콜로 통신을 한다. 그러므로 아래의 코드는 HttpManager코드와 NetUtil 코드로 나누어 구현 하였다.

// HttpClientManager.h
#ifndef __HTTP_CLIENT_MANAGER_H_
#define __HTTP_CLIENT_MANAGER_H_

#include <string>
#include <strings.h>

#include "json/json.h"

typedef struct ResultRes_s
{
    char  version[10];
    int   resultCode;
    char  resultMsg[50];
} ResultRes_t;

class HttpClientManager
{
public:
    HttpClientManager();
    ~HttpClientManager();

    int     connect();
    void    disconnect();

    int     sendTest();
    bool    getResResultCode();

private:
    int	         connect2(int sock, const char* ipaddr, int port, int timeout, int fd_int = -1);
    void         httpParser(char* resData);
    void         processBody(char* start);
    int          sendRequest(Json::Value& data, const char* url);
    std::string  setRequestBody(Json::Value& data);

private:
    int     mSock;
    int     mPipe[2];

    ResultRes_t mResultRes;
};

 

// HttpClientManager.cpp
#include "HttpClientManager.h"

#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>

#ifndef SAFE_CLOSE
#define SAFE_CLOSE(fd) if (fd > 0) { ::close(fd); fd = -1; }

#define REQUEST_FMT \
"POST %s HTTP/1.0\r\n" \
"Host: %s:%d\r\n" \
"Agent: test\r\n" \
"Accept: */*\r\n" \
"Content-Type: application/json\r\n" \
"Content-Length: %ld\r\n"\
"\r\n" \
"%s"

#define ISSPACE(c)     ((c)==' ' || (c)=='\f' || (c)=='\n' || (c)=='\r' || (c)=='\t' || (c)=='\v')

char* ltrim(char *s)
{
    if(!s) return s;

    while(ISSPACE(*s)) s++;

    return s;
}

char* rtrim(char* s)
{
    char* back;

    if(!s) return s;

    back = s + strlen(s);

    while((s <= --back) && ISSPACE(*back));
    *(back + 1)    = '\0';

    return s;
}

HttpClientManager::HttpClientManager()
                    :mSock(-1),
                     mResCode(0)
{
    mPipe[0] = -1;
    mPipe[1] = -1;
}

HttpClientManager::~HttpClientManager()
{
    SAFE_CLOSE(mSock);
    SAFE_CLOSE(mPipe[0]);
    SAFE_CLOSE(mPipe[1]);
}

int HttpClientManager::connect()
{    
    mSock = socket(AF_INET, SOCK_STREAM, 0);
    if(mSock < 0 )
    {
        printf("socket failed\n");
        return -1;
    }
    
    if(NetUtil::connect2(mSock, HTTP_THERMAL_IP, HTTP_SERVER_PORT, CONN_TIMEOUT) != 0)
    {
        printf("socket connect failed\n");
        return -1;
    }

    return 0;
}

void HttpClientManager::disconnect()
{   
    close(mSock);
}

void HttpClientManager::httpParser(char* resData)
{
    char line[4*1024];
    char* start, *end;

    start = resData;

    while ((end = strstr(start, "\r\n")))
    {
        int len = end - start;
        strncpy(line, start, len);
        line[len] = 0;
        start = end + 2;

        if (len == 0) // Body
            break;

        if (strncmp(line, "HTTP", 4) == 0)
        {
            sscanf(line, "%s %d %s", mResultRes.version, &mResultRes.resultCode, mResultRes.resultMsg);
            printf("%s %d %s\n", mResultRes.version, mResultRes.resultCode, mResultRes.resultMsg);
        }
        else
        {
            char* name, *value;
            
            value = strchr(line, ':');
            if (value)
            {
                *value = 0;
                value = ltrim(rtrim(value + 1));
            }
            name = ltrim(rtrim(line));
            printf("%s - %s\n", name, value);
        }
    }

    if (strlen(start) > 0) // Process Body
    {
        processBody(start);
    }
}

void HttpClientManager::processBody(char* start)
{
    printf("body Value : \n%s\n", start);
    Json::CharReaderBuilder builder;
    Json::CharReader *reader = builder.newCharReader();
    Json::Value bodyValue;
    reader->parse(start, start+strlen(start), &bodyValue, NULL);

    const Json::Value& data = bodyValue["data"];

    if(!data["test"].isNull())
    {
        const Json::Value& test = data["test"];
        printf("test : %s\n", test.asCString());
    }

    mResCode    = bodyValue["result"].asInt();
    printf("body ResultCode : %d\n", mResCode);
    mResMessage = bodyValue["message"].asString();
    printf("body ResultMsg : %s\n", mResMessage.c_str());
}

int HttpClientManager::sendTest()
{
    char url[100]       = "/test";

    Json::Value data;
    data["device-type"] = "windows";
    data["api-version"] = "0.1";

    return sendRequest(data, url);
}

int HttpClientManager::sendRequest(Json::Value& data, const char* url)
{
    int         ret;
    int         req;
    char        request[1024];
    std::string reqBody;

    if (connect() == -1)
    {
        printf("Http Connecetion Failed\n");
        goto EXIT;
    }

    ret = NetUtil::fd_poll(mSock, POLL_REQ_OUT, 3000, mPipe[0]);
    if (ret != POLL_SUCCESS)
    {
        printf("failed to poll. ret : %d\n", ret);
        goto EXIT;
    }

    reqBody = setRequestBody(data);

    sprintf(request, REQUEST_FMT, url, HTTP_THERMAL_IP, HTTP_SERVER_PORT, strlen(reqBody.c_str()), reqBody.c_str());
    printf("req Data :\n%s\n", request);
    
    req = send(mSock, &request, sizeof(request), MSG_NOSIGNAL);
    if (req != sizeof(request))
    {
        printf("send failed : %d\n", req);
        goto EXIT;
    }

    if(!getResResultCode())
        printf("Http Response Failed\n");

    disconnect();

    return req;

EXIT:
    disconnect();
    return -1;
}

std::string HttpClientManager::setRequestBody(Json::Value& data)
{
    Json::Value               body;
    Json::Value               option;
    Json::StreamWriterBuilder builder;

    body["data"] = data;
    builder["indentation"] = "";

    return Json::writeString(builder, body);
}

bool HttpClientManager::getResResultCode()
{
    NetUtil::fd_poll(mSock, POLL_REQ_IN, 5000, mPipe[0]);

    char response[1024];
    int ret = recv(mSock, &response, sizeof(response), 0);
    if (ret < 0)
    {
        printf("failed to recv packet. ret : %d\n", ret);
        return false;
    }

    response[ret] = 0;
    printf("res Data :\n%s\n", response);

    httpParser(response);

    if(mResultRes.resultCode == 200 && mResCode == 2000)
        return true;
    return false;
}
// NetUtil.h
#ifndef __NET_UTIL_H_
#define __NET_UTIL_H_

#include <stdint.h>
#include <poll.h>

#define POLL_SUCCESS     (1)
#define POLL_INTERRUPTED (2)
#define POLL_TIMEOUT     (0)

#define POLL_REQ_IN      (POLLIN)
#define POLL_REQ_OUT     (POLLOUT)

class NetUtil
{
public:
    int poll2(struct pollfd *fds, nfds_t nfds, int timeout);
    int fd_poll(int fd, int req, int timeout, int fd_int = -1);

    int connect2(int sock, const char* ipaddr, int port, int timeout, int fd_int = -1);
    int bind2(int sock, const char* ipaddr, int port);

    /* Socket Option */
    int socket_set_blocking(int sock, bool block);
};
//NetUtil.cpp
#include "NetUtil.h"

#include <signal.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>

#include <net/if.h>
#include <sys/ioctl.h>

int NetUtil::poll2(struct pollfd *fds, nfds_t nfds, int timeout)
{
    int ret = 0;
    sigset_t org_mask;
    sigset_t new_mask;

    memset(&org_mask, 0, sizeof(org_mask));
    memset(&new_mask, 0, sizeof(new_mask));

    sigaddset(&new_mask, SIGHUP);
    sigaddset(&new_mask, SIGALRM);
    sigaddset(&new_mask, SIGUSR1);
    sigaddset(&new_mask, SIGUSR2);

    sigprocmask(SIG_SETMASK, &new_mask, &org_mask);
    ret = poll(fds, nfds, timeout);
    sigprocmask(SIG_SETMASK, &org_mask, NULL);

    return ret;
}

int NetUtil::fd_poll(int fd, int req, int timeout, int fd_int)
{
    int ret = 0;
    struct pollfd fds[2];
    nfds_t nfds = 1;

    fds[0].fd = fd;
    fds[0].events = (short)req | POLLRDHUP | POLLERR | POLLHUP | POLLNVAL;
    fds[0].revents = 0;

    if (fd_int >= 0)
    {
        fds[1].fd = fd_int;
        fds[1].events = POLLIN;
        fds[1].revents = 0;
        nfds = 2;
    }

    ret = poll2(fds, nfds, timeout);
    if ( ret == 0 )
        return POLL_TIMEOUT;    /* poll timeout */

    if ( ret < 0 )
    {
        printf("poll failed. ret=%d, errno=%d.\n", ret, errno);
        return -errno;    /* poll failed */
    }

    if (nfds == 2 && fds[1].revents & POLLIN)
    {
        char buf[32];
        (void) read(fd_int, buf, sizeof(buf));
        return POLL_INTERRUPTED;
    }

    if ( fds[0].revents & (POLLRDHUP | POLLERR | POLLHUP | POLLNVAL) )
    {
        return -1;
    }
    
    if (fds[0].revents & req)
    {
        return POLL_SUCCESS;
    }

    return -1;
}

int NetUtil::connect2(int sock, const char* ipaddr, int port, int timeout, int fd_int)
{
    int ret;
    struct sockaddr_in serv_addr;

    socket_set_blocking(sock, false);

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(port);

    printf("connect to %s:%d\n", ipaddr, port);
    
     if (inet_pton(AF_INET, ipaddr, &serv_addr.sin_addr) <= 0)
    {
        printf("Invalid address/ Address not supported \n");
        return -1;
    }

    ret = connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr));

    if (errno > 0 && errno != EAGAIN && errno != EINPROGRESS)
    {
        printf("Cannot connect !, errno = %d\n", errno);
        ret = -1;
    }

    ret = fd_poll(sock, POLLOUT, timeout, fd_int);
    if (ret == POLL_SUCCESS)
    {
        printf("Connection is established.\n");
        ret = 0;
    }
    else if (ret == POLL_TIMEOUT) // TIMEOUT
    {
        printf("Connecton timeout ... !\n");
        ret = -1;
    }
    else if (ret == POLL_INTERRUPTED)
    {
        printf("!!! INTERUPTED !!!!\n");
        ret = -1;
    }
    else
    {
        printf("Poll error : %d\n", ret);
        ret = -1;
    }

    socket_set_blocking(sock, true);

    return ret;
}

int NetUtil::bind2(int sock, const char* ipaddr, int port)
{
    struct sockaddr_in addr;

    addr.sin_family = AF_INET;
    addr.sin_port = htons (port);

    if (!ipaddr
      || strcmp(ipaddr, "127.0.0.1") == 0
      || strcmp(ipaddr, "localhost") == 0)
    {
        addr.sin_addr.s_addr = INADDR_ANY;
    }
    else
    {
        if (inet_pton(AF_INET, ipaddr, &addr.sin_addr) <= 0)
        {
            printf("Invalid address. %s\n", ipaddr);
            return -1;
        }
    }
    if (bind(sock, (struct sockaddr*)&addr, sizeof(addr)) != 0)
    {
        printf("bind failed. errno : %d(%s)\n", errno, strerror(errno));
        return -1;
    }

    return 0;
}

int NetUtil::socket_set_blocking(int sock, bool block)
{
    int flags = fcntl(sock, F_GETFL, 0);
    if(block)
        flags = flags & (O_NONBLOCK ^ 0xFFFFFFFF);
    else
        flags |= O_NONBLOCK;

    if (fcntl(sock, F_SETFL, flags) < 0)
    {   
        printf("Cannot set socket blocking\n");
        return -1;
    }

    return 0;
}

 

'Embedded SW 기초 > 네트워크' 카테고리의 다른 글

TCP, UDP 통신  (0) 2024.06.22
네트워크 구조  (0) 2024.06.12
IPC와 RPC 차이  (0) 2024.06.12