Web Workers 를 공부하다 SharedWorker 는 각 스크립트들이 MessagePort 를 통해 데이터 전달을 한다는 것을 알게 되었고 관련하여 공부를 하게 됨

MessagePort 는 MessageChannel 간에 데이터를 전달하고 받을 수 있도록 하는 두 개의 ports 중 한 개를 나타냄.

MessageChannel 생성후 channel.port1channel.port2 를 통해 데이터를 전달하고 받을 수 있음

// main.js
const channel = new MessageChannel();

iframe.addEventListener("load", onLoad);
function onLoad() {
  channel.port1.onmessage = function(e) { 
  }

  iframe.contentWindow.postMessage("Hello", "*", [channel.port2]);
}

// iframe.html
<script>
  window.addEventListener('message', onMessage);
  function onMessage(e) {
    e.ports[0].postMessage('Message Back');
  }
</script>

postMessage() 함수의 세번째 인자 transfer 를 사용하면 객체의 소유권을 수신측에 전달함. 송신 측에서는 더 이상 사용 불가능

function onLoad() { 
  channel.port1.onmessage = function(e) {  
  } 
  iframe.contentWindow.postMessage("Hello One", "*", [channel.port2]);
  iframe.contentWindow.postMessage("Hello Two", "*", [channel.port2]); // Uncaught DOMException: Failed to execute 'postMessage' on 'Window': Port at index 0 is already neutered.
}

'IT > Web' 카테고리의 다른 글

[Web API] Web Workers API  (0) 2022.09.14

스크립트 연산을 주 실행 스레드와 분리된 별도의 백그라운드 스레드에서 실행할 수 있는 기술

종류

  • 전용 워커 - 단일 스크립트에서만 사용하는 워커 : DedicatedWorkerGlobalScope
  • 공유 워커 - 여러 스크립트에서 공유하는 워커 : SharedWorkerGlobalScope
    • SharedWorker
  • 서비스 워커 - 웹 응용 프로그램, 브라우저, 네트워크 사이의 프록시 서버 역할
    • 네트워크 요청 인터셉트
    • 푸시 알람
    • 백그라운드 동기화
  • 오디오 워커 - 직접적인 오디오 처리 능력 제공

제한사항

  1. 워커에서 DOM 직접 조작 불가능
  2. window 의 일부 메소드 사용 불가능
  3. WebSocket 과 IndexedDB 는 사용 가능

데이터 교환

워커와 메인 스레드 간의 데이터 교환은 메세지 시스템 사용 (postMessage, onmessage) 데이터는 복사하고 공유는 하지 않음

Example

!! 워커 생성 시 Worker() 생성자 사용함. 워커 스레드 생성시에는 현재 window 와는 다른 전역 Context 에서 생성해야 동작함
(테스트 위해서 $ npx lite-server 이용했음)


전용워커)

// main.js
if (window.Worker) {
  const myWorker = new Worker("worker.js");
  const payload = {flag: 1}
  myWorker.postMessage(payload);

  myWorker.onmessage = function(e) {
    if (payload.data == e.data) {
      console.log('Never called');
    } else {
      console.log('Always called');
    }
  }
}

// worker.js
onmessage = function(e) {
  console.log(e.data) // {flag: 1}

  e.data.flag = 2;  // 데이터를 공유하지 않기에 main.js 의 payload 객체에 영향을 주지 않음

  postMessage(e.data.flag);
}

공유워커)

// main.js
if (window.SharedWorker) {
  const myWorker = new SharedWorker("worker.js");
  const payload = {flag: 1}
  myWorker.port.postMessage(payload);
  myWorker.port.onmessage = function(e) {
    console.log(e.data);
  }
}

// worker.js
onconnect = function (event) {
  const port = event.ports[0];

  port.onmessage = function(e) {
    port.postMessage('');
  }
}

'IT > Web' 카테고리의 다른 글

[WebAPI] MessagePort  (0) 2022.09.14

테스트 환경

  • Zookeeper 3대를 앙상블로 구현
  • Kafka 의 server.properties 에 zookeeper 설정

설정 과정

  1. 한 서버에서 Zookeeper 프로세스 3대를 띄어서 앙상블로 구현
  2. Kafka 의 server.properties 설정 정보]
zookeeper.connect=localhost:2181,localhost:2182,localhost:2183

테스트


토픽 생성 Command

$ /path/kafka/kafka-topics.sh --create --topic test-topic --bootstrap-server localhost:9092 --replication-factor 1 --partitions 4

Zookeeper 서비스를 종료 (1개부터 2개까지 종료시키기)

- Zookeeper 프로세스 3개 중에서 1개를 종료

$ systemctl stop zookeeper1
$ /path/kafka/kafka-topics.sh --create --topic test-topic --bootstrap-server localhost:9092 --replication-factor 1 --partitions 4

결과) Created topic test-topic

- Zookeeper 프로세스를 3개 중에서 2개까지 종료

$ systemctl stop zookeeper2 
$ /path/kafka/kafka-topics.sh --create --topic test-topic --bootstrap-server localhost:9092 --replication-factor 1 --partitions 4  

결과) 토픽 생성하지 못하고 계속 대기...

- Zookeeper 프로세스를 다시 2개 이상으로 살리기

$ systemctl start zookeeper2

결과) 이후에 토픽 생성 명령어를 실행하면, 정상적으로 수행이 된다.

Mysql UDF 작성

1) 공유 라이브러리 파일 작성

윈도우는 lib, linux/unix 계열은 so 파일을 만들어야 한다.

CMake 를 이용하는게 더 간편하기에 자주 사용하는 명령어에 대해서만 간략하게 정리한다.

# cmake 최소 요구 버전 - 이하일시 에러 발생 CMAKE_MINIMUM_REQUIRED (VERSION 3.5)  # CMAKE_PROJECT_NAME 설정 PROJECT(name)  # project 이름을 콘솔에 출력 MESSAGE(${CMAKE_PROJECT_NAME})  # 전처리기 매크로 추가 ADD_DEFINITIONS(-fPIC)  # 바이너리 생성 ADD_EXECUTABLE(app foo.c bar.c)  # 라이브러리 생성 ADD_LIBRARY(app SHARED foo.c bar.c )  # 각 소스 파일에서 #include 구문으로 포함시킨 헤더 파일을 찾을 디렉토리 추가 INCLUDE_DIRECTORIES(include /usr/local/include)  # 링크 과정에서 필요한 라이브러리 디렉토리 목록 추가 LINK_DIRECTORIES(lib /usr/local/lib)  # Target에 포함된 소스 파일에서 #include 구문에 포함된 헤더 파일 찾을 디렉토리 추가 TARGET_INCLUDE_DIRECTORIES(include /usr/local/include) 

더 다양한 명렁에 대한 정리는 다음 블로그를 참조하면 된다.

CMake 명령어 정리
CMake Tutorial

2) UDF 작성

위에서 CMAKE를 작성하는 법에 대해 알아보았다. 이제 공유 라이브러리를 만들기 위해 필요한 소스 파일을 작성해야 한다.

Mysql UDF 는 2가지 종류로 작성할 수 있다.

  • xxx_init() -> xxx() -> xxx_deinit()
  • xxx_init() -> xxx_clear() -> xxx_add() -> xxx() -> xxx_deinit()

xxx()는 본문 함수이고 UDF를 추가하기 위해서는 위의 형식을 지키며 함수를 추가해줘야 한다. 첫번째 형식이 가장 일반적인 경우이다.

두번째 형식은 Aggregate UDF라고 Group으로 xxx() 함수를 적용시킬 수 있는 경우이다.

ex) select sum(cost) from table group by dept;

Header 추가

먼저 각 인자들의 Type 을 위해 mysql 헤더를 추가한다.

#include <mysql.h> 

init, deinit

기본적으로 init, deinit 과정이 있으며 각각 다음과 같은 역할을 한다.

init

  • set default properties
  • validate arguments
  • allocate resources
  • signal an error

deinit

  • de-allocate any resources
my_bool xxx_init(UDF_INIT * initid, UDF_ARGS * args, char * message) 
void xxx_deinit(UDF_INIT * initid); 

본문 함수 작성

xxx() 는 본문함수이며 return 값에 따라 2가지 타입으로 선언할 수 있다.

// Integer Type long long xxx(UDF_INIT *initid, UDF_ARGS *args, my_bool *is_null, my_bool *error);  // Real type double xxx(UDF_INIT *initid, UDF_ARGS *args, my_bool *is_null, my_bool *error);  // String type, DECIMCAL type도 동일하게 작성 char * xxx(UDF_INIT *initid, UDF_ARGS * args, char * result, unsigned long length, my_bool *is_null, my_bool *error); 

string return value에 대해서만 더 자세하게 적도록 한다.

char * xxx(UDF_INIT *initid, UDF_ARGS * args, char * result, unsigned long length, my_bool *is_null, my_bool *error) {     // xxx(arg1, arg2) 입력 파라미터 값을 받을 수 있음.     char arg1 = args->args[0];      char arg2 = args->args[1];      // 로직 작성     char *value = fn();      // 지역변수를 return하게 되면 안된다. result에 담아서 보내야 한다.     strcpy(result, value);      return result; } 

다음 링크를 참고하면 Mysql에서는 xxx() 함수에 대해 result라는 버퍼를 제공하는데(최적화 이유) 255 크기로 제한이 되어 있다. -- 운영체제와 mysql 메모리 관리 방식은 따로 공부를 해봐야겠다.

mysql udf return values

만약 255 이상의 데이터를 보내고 싶다면 어떻게 해야 할까? UDF_INIT * initid 인자를 이용해야 한다.

UDF_INIT 구조체는 result에 대한 정보를 정의할 수가 있다.

mysql UDF_INIT Ref

만약 xxx_init()에서 mysql에서 제공하는 buffer(result) 를 이용하지 않고 새로 만들었다면 아래와 같이 사용가능하다.

xxx_init(..) {     char * arr = malloc(1024);     initid->ptr = arr;     initid->max_length = 1024; }  char * xxx(UDF_INIT *initid, UDF_ARGS * args, char * result, unsigned long length, my_bool *is_null, my_bool *error) {     char * buffer = initid->ptr;      return buffer; }  xxx_deinit(..) {     char * arr = initid->ptr;      if(arr) {         free(arr);     } } 

3) Mysql 에 UDF 추가

linux 를 기준으로 설명하면 /usr/local/mysql/lib/plugin 디렉토리가 존재한다.

생성한 so 파일을 해당 디렉토리에 넣어준다.

다음 명령을 통해 udf 를 추가할 수 있다.

create function func1 returns string soname 'lib_mysqludf.so'; create function func2 returns integer soname 'lib_mysqludf.so'; 

정리

CMake부터 시작하여 UDF를 작성하여 mysql에 추가해보는 시간을 가졌다. 실습을 해보면서 링크 및 컴파일에 대한 개념이 부족함을 알게 되었고, Mysql과 운영체제에서의 메모리 관리 방식에 대해서도 공부를 해봐야 겠다는 생각이 들었다.

Aggregate UDF 에 대해서는 다른 블로그를 참고하면서 직접 구현해보면 좋을 것 같다. clear를 기준으로 group이 나뉜다고 생각하면 쉽다.

Selenium_install

이 포스팅은 AWS EC2 ubuntu에 Python과 Selenium을 설치하여 연동하는데 도움을 줄 수 있는 글입니다.

Python Install

apt로 python을 설치한다.

sudo apt install python

만약 아래와 같은 에러가 뜬다면

sudo apt-get install python-pip
Reading package lists... Done
Building dependency tree
Reading state information... Done
E: Unable to locate package python-pip

python-pip 가 universe repositories에 있기 때문에 local repo에다가 추가해줘야 한다.

sudo apt-get install software-properties-common
sudo apt-add-repository universe
sudo apt-get update
sudo apt-get install python-pip

Selenium Instal On Ubuntu

  1. Chrome Driver 설치

  2. ./install.sh 실행

만약 아래와 같은 에러가 뜬다면

※ selenium.common.exceptions.WebDriverException:
Message: unknown error: cannot find Chrome binary
// Add Key
$ wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo apt-key add -

// Set repo
$ echo 'deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main' | sudo tee /etc/apt/sources.list.d/google-chrome.list

// Install package
$ sudo apt-get update
$ sudo apt-get install google-chrome-stable

Chrome Headless 사용

GUI 가 지원이 안되는 ubuntu에서 chrome driver를 이용하면 에러가 나는 경우가 있다.

이 경우에는 아래와 같이 headless option을 설정해줘야 한다.

options = webdriver.ChromeOptions()
    options.add_argument('--disable-extensions')
    options.add_argument('--headless')
    options.add_argument('--disable-gpu')
    options.add_argument('--no-sandbox')
    return webdriver.Chrome(chrome_options=options)

'IT > BackEnd' 카테고리의 다른 글

Mysql UDF 작성  (0) 2019.03.20
SSL 인증서 발급 및 Nginx 적용. 그리고 체인 이슈 해결하기  (1) 2018.02.28
Atom Remote-FTP package 소개  (0) 2017.11.02
JWT  (0) 2017.09.27

XMLHttpRequest란?

XMLHttpRequest는 HTTP를 통해서 쉽게 데이터를 받을 수 있게 해주는 오브젝트를 제공한다.

Ajax로 실행되는 HTTP 통신도 XMLHttpRequest 규격을 이용하고 있다.

How to use?

var xhr = new XMLHttpRequest();

xhr.open('GET', 'http://localhost:3000/', true);

xhr.send(null);

위 코드를 사용하게 되면 http://localhost:3000 로 비동기로 HTTP 요청을 하게 된다.

정리하면 아래와 같다.

  1. XMLHttpRequest()를 통해 오브젝트 생성을 한다.

  2. 그 후 open() 함수를 통해 메소드 방식, url, 그리고 비동기 여부를 설정한다.

  3. send(data) 로 설정한 방식대로 request를 보낸다.(data는 null이어도 된다.)

How to response?

xhr.onreadystatechange = function()
{
    if(xhr.readyState == XMLHttpRequest.DONE && xhr.status == 200) {
        console.log(xhr.responseText);
    }
}

위 코드를 사용하게 XMLHttpRequest 오브젝트에 onreadystatechange를 등록하면 request 후 해당 server로부터 status code가 200이면 response 받은 text를 콘솔에 출력한다.

How to send data?

xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');

var data = 'id=' + id + '&' + 'pw' + pw;
xhr.send(data);

x-www-form-urlencoded로 Content-type을 지정하면 data는 form 형식을 유지해야 한다.

xhr.setRequestHeader('Content-Type', 'application/json');

var data = {
  id: id,
  pw: pw
}

xhr.send(data);

application/json 타입이면 data는 json 타입이어야 한다.

'IT > FrontEnd' 카테고리의 다른 글

ajax를 사용하기 그리고 사용하며..  (0) 2018.01.13

SSL 발급받기

SSL 인증서를 구입하기 위해 저렴한 사이트들이 많다. 나는 GOGETSSL에서 인증서를 구입했다.

SSL 인증서 발급받기 위한 자세한 사항은 아래의 링크에서 참고 하기 바란다.

GOGETSSL에서 인증서 발급받기

요점을 정리하면, 인증서 등록시 CSR Generate 를 클릭하게 되면 CSR Code와 Server private key가 생성이 된다.

private key에 필요한 정보를(적용할 도메인 url) 입력한 다음 단계를 진행하다 보면 도메인 소유 인증을 진행한다. 각자 도메인을 구입한 사이트에서 E-mail, DNS, HTTP, HTTPS 기반의 인증방식을 제공하니 메뉴얼대로 진행하면 된다.

(주의해야할 점은 기존의 private key는 한번 잃어버리면 다시 재발급이 안된다. CSR/private Key pair을 다시 생성해야 한다.)

나는 DNS 인증방식으로 진행하였고 제공된 CNAME 코드로 DNS 서버의 CNAME Recorde를 등록하였다.

입력한 인증 정보를 통해 인증 진행후 인증서 파일을 다운로드를 받을 수 있다.

Nginx에 적용하기

/etc/nginx/site-available 폴더에서 server block 이 설정된 파일을 들어가보자.

기본으로 아래와 같이 되어 있다. 아래의 코드의 의미를 살펴 보면 80번 포트로(HTTP프로토콜의 기본 포트) example.com 도메인으로 요청이 들어오면 해당하는 location에 명시된 행위를 하겠다는 것을 의미한다.

server {
  listen 80;
  server_name example.com;

  location / {

  }
}

우리는 SSL을 적용해야 하기 때문에 HTTPS의 기본포트인 443 포트를 적용해야 한다.

server {
  listen 443;
}

그리고 SSL 인증서 발급시 생성된 key 파일과 crt 파일을 서버에 위치시키고 다음과 같이 작성한다.

server {
  listen 443;
  server_name example.com
  root html;

  ssl       on;
  ssl_certificate /etc/nginx/ssl/my_domain.crt;
  ssl_certificate_key /etc/nginx/ssl/my_domain.key;
  ssl_prefer_server_ciphers on;
  ssl_session_timeout 5m;

  location / {

  }
}

보안을 위해 nginx에 ssl폴더에 넣었지만 보안을 위해 루트 권한 설정을 한다.

$ sudo chown -R root:root /etc/nginx/ssl;

이제 https://example.com 으로 접속하게 되면 접속이 되는 것을 확인할 수 있다.

Chain Error Issue

일반 최신 웹 브라우저에서 접속하면 정상적으로 인증이 통과되어 접속이 된다. 하지만 구형 웹브라우저나 일부 모바일기기의 웹뷰에서는 접속이 안되는 경우가 있다. 이를 위해 먼저 체인 인증서에 대해 알아보자

체인 인증서란?

루트 인증서와 발급 받은 서버 인증서 사이에 존재하는 인증서이다. 즉 루트 인증서와 서버 인증서 사이를 중개, 연결해준다는 것이다. SSL 동작 방식에 의거하여 웹 브라우저에서 CA라는 Root Certificate 에서 통과된 인증서인지를 확인 후 접속을 허락해준다.

근데 이 체인 인증서라는게 SSL 초장기에는 루트 인증서에서 바로 서버 인증서를 발급하였지만, 최신에는 기술 환경 변화에 맞게 체인인증서 발급 체계를 가지고 있다.

따라서 최신 웹 브라우저의 경우에는 최신 루트 인증서 뿐만 아니라 체인 인증서까지 보유하고 있지만, 구형 웹 브라우저나 일부 모바일 기기에서는 접속이 안되는게 이러한 이유가 존재한다.

체인 이슈 해결

서버쪽에서 명시적으로 현재 서버인증자가 누구이며 어떤 발급 단계를 통하였는지 알려주면 되는 것이다.



위 사진에서 전체 zip 파일을 다운 받는다. 그러면 내부에 3가지의 파일이 존재할 것이다.

my_domain.crt, AddTrust_External_CA_Root.crt, COMODO_RSA_Certification_Authority.crt

nginx CA 인증서를 명시할 지시자 없으므로 다음과 같이 SSL 인증서와 CA 인증서를 하나의 파일로 만들어야 한다.

$ cat my_domain.crt AddTrust_External_CA_Root.crt COMODO_RSA_Certification_Authority.crt > ssl-bundle

그리고 가끔씩 순서를 잘 못 맞춰서 key와 crt파일이 매칭이 되지 않는 경우가 있는데 이 때는 sslshopper 에서 매칭 여부를 확인할 수 있다.

이제 SSL 인증서와 CA 인증서를 하나로 합친 파일을 nginx에 등록시키자.

server {
  listen 443;
  server_name example.com
  root html;

  ssl       on;
  ssl_certificate /etc/nginx/ssl/ssl-bundle.crt;
  ssl_certificate_key /etc/nginx/ssl/my_domain.key;
  ssl_prefer_server_ciphers on;
  ssl_session_timeout 5m;

  location / {

  }
}

자, 그러면 체인 문제가 해결되었는지 sslshopper에서 확인해보자.

그림처럼 오른쪽에 Server hostname을 입력하고 확인해 보 수 있다.

'IT > BackEnd' 카테고리의 다른 글

Mysql UDF 작성  (0) 2019.03.20
Crawling을 위한 Selenium, Chrome Driver 설치  (0) 2018.07.15
Atom Remote-FTP package 소개  (0) 2017.11.02
JWT  (0) 2017.09.27

서브 도큐먼트 검색 후 값 변경 시키기

다음과 같이 서브 스키마가 정의되어 있는 부모 스키마가 있다고 가정해보자.

A = {
  B = [
    new Schema({
      content: String
    })
  ]
}

다음과 같은 query로 B의 unique id를 가진 부모 도큐먼트를 검색할 수 있다.

A.findOneAndUpdate({"B._id": sub_uid})

만약, 찾은 후에 서브 도큐먼트의 값을 변경시키고 싶다면 어떻게 해야할까? 방법이야 많겠지만 내가 찾은 방법은 아래의 방법이다.

A.findOneAndUpdate({"B._id": sub_uid}, {$set: {"B.$.content": content}})

정리

다음의 .$. 을 이용하여 $set 옵션 사용시 해당 필드의 값을 변경할 수 있다. 서브 도큐먼트 없이 부모 도큐먼트 하나로 값을 관리할 수 있다면 좋겠지만, 가끔씩 서브 도큐먼트를 사용해야 할 경우가 생기면 위와 같은 방법을 사용해면 좋겠다.

'IT > Mongodb' 카테고리의 다른 글

도큐먼트 필드 삭제하기  (0) 2017.12.30
$elemMatch vs $in  (0) 2017.10.31
ISO Date 값이 한국 시간과 다를때  (1) 2017.10.31
스키마 모델링 어떻게 해야 하나  (0) 2017.10.21

+ Recent posts