Seize the day

SEARCH RESAULT : 글 검색 결과 - 전체 글 (총 474개)

POST : Backend study

Google GCP 우분투에 Nginx/Mongodb/Deno 설치하기

시작 부터 에러가

kimdaejeong@base-backend-1:~$ sudo apt-get install nginx
E: Could not get lock /var/lib/dpkg/lock-frontend - open (11: Resource temporarily unavailable)
E: Unable to acquire the dpkg frontend lock (/var/lib/dpkg/lock-frontend), is another process using it?

 

구글링해서

E: Could not get lock /var/lib/dpkg/lock-frontend - open (11: Resource temporarily unavailable)
E: Unable to acquire the dpkg frontend lock (/var/lib/dpkg/lock-frontend), is another process using it?

 

위 에러를 아래와 같이 해결했습니다.

우선 터미널 여시고 모든 프로세스를 죽여줍니다~!
1) sudo killall apt apt-get

만일 진행중인 프로세스가 없다라고 뜨면, 아래와 같이 하나하나씩 디렉토리를 삭제해주세요.

- sudo rm /var/lib/apt/lists/lock
- sudo rm /var/cache/apt/archives/lock
- sudo rm /var/lib/dpkg/lock*

sudo dpkg --configure -a  를 하시고 sudo apt update

 

sudo dpkg --configure -a    에서 다시 에러

kimdaejeong@base-backend-1:~$ sudo dpkg --configure -a 
dpkg: error: dpkg status database is locked by another process
kimdaejeong@base-backend-1:~$ sudo rm /var/lib/dpkg/lock
kimdaejeong@base-backend-1:~$ sudo dpkg --configure -a 
dpkg: error: dpkg status database is locked by another process

 

lsof /var/lib/dpkg/lock 으로 해결된 듯.. 왜 때문에?
dpkg: error: dpkg status database is locked by another process - Ask Ubuntu

 

설치 성공

kimdaejeong@base-backend-1:~$ nginx -V
nginx version: nginx/1.10.3 (Ubuntu)
built with OpenSSL 1.0.2g  1 Mar 2016
TLS SNI support enabled
configure arguments: --with-cc-opt='-g -O2 -fPIE -fstack-protector-strong -Wformat -Werror=format-security -Wdate-time -D_FORTIFY_SOURCE=2' --with-ld-opt='-Wl,-Bsymbolic-functions -fPIE -pie -Wl,-z,relro -Wl,-z,now' --prefix=/usr/share/nginx --conf-path=/etc/nginx/nginx.conf --http-log-path=/var/log/nginx/access.log --error-log-path=/var/log/nginx/error.log --lock-path=/var/lock/nginx.lock --pid-path=/run/nginx.pid --http-client-body-temp-path=/var/lib/nginx/body --http-fastcgi-temp-path=/var/lib/nginx/fastcgi --http-proxy-temp-path=/var/lib/nginx/proxy --http-scgi-temp-path=/var/lib/nginx/scgi --http-uwsgi-temp-path=/var/lib/nginx/uwsgi --with-debug --with-pcre-jit --with-ipv6 --with-http_ssl_module --with-http_stub_status_module --with-http_realip_module --with-http_auth_request_module --with-http_addition_module --with-http_dav_module --with-http_geoip_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_image_filter_module --with-http_v2_module --with-http_sub_module --with-http_xslt_module --with-stream --with-stream_ssl_module --with-mail --with-mail_ssl_module --with-threads

 

80 포트로 변경하고 서버 시작 

$ vi  /etc/nginx/sites-enabled/default
$ sudo service nginx start

 

Certbot을 이용하여 Ngix에 SSL 인증서 적용하기

인증서 만들기
Certbot으로 무료 인증서 발급 받기 | MHLab Blog (elfinlas.github.io)
Ubuntu Nginx 환경에서 CertBot을 사용하여 https 사용하기 (velog.io)
[Nginx] Nginx HTTPS 및 Let's Encryt SSL 인증서 적용⋆ JackerLab
여기를 참고..

kimdaejeong@base-backend-1:/var/www/html/.well-known/acme-challenge$ sudo certbot certonly --webroot -w /var/www/html -d app.mdiwebma.com
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Plugins selected: Authenticator webroot, Installer None
Starting new HTTPS connection (1): acme-v02.api.letsencrypt.org
Obtaining a new certificate
Performing the following challenges:
http-01 challenge for app.mdiwebma.com
Using the webroot path /var/www/html for all unmatched domains.
Waiting for verification...
Cleaning up challenges

IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at:
   /etc/letsencrypt/live/app.mdiwebma.com/fullchain.pem
   Your key file has been saved at:
   /etc/letsencrypt/live/app.mdiwebma.com/privkey.pem
   Your cert will expire on 2021-11-01. To obtain a new or tweaked
   version of this certificate in the future, simply run certbot
   again. To non-interactively renew *all* of your certificates, run
   "certbot renew"
 - If you like Certbot, please consider supporting our work by:

   Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
   Donating to EFF:

설치 과정중에 http 서버가 실행중이어야한다.
/var/www/html 이 현재  http를 서비스하고 있는 nginx의 root 디렉토리이다.

인증서는 /etc/letsencrypt에 있다.

kimdaejeong@base-backend-1:/etc/letsencrypt$ sudo ls -al /etc/letsencrypt/live/app.mdiwebma.com
total 12
drwxr-xr-x 2 root root 4096 Aug  3 11:34 .
drwx------ 3 root root 4096 Aug  3 11:34 ..
lrwxrwxrwx 1 root root   40 Aug  3 11:34 cert.pem -> ../../archive/app.mdiwebma.com/cert1.pem
lrwxrwxrwx 1 root root   41 Aug  3 11:34 chain.pem -> ../../archive/app.mdiwebma.com/chain1.pem
lrwxrwxrwx 1 root root   45 Aug  3 11:34 fullchain.pem -> ../../archive/app.mdiwebma.com/fullchain1.pem
lrwxrwxrwx 1 root root   43 Aug  3 11:34 privkey.pem -> ../../archive/app.mdiwebma.com/privkey1.pem
-rw-r--r-- 1 root root  692 Aug  3 11:34 README

 

nginx 설정파일 변경

server {
        # listen 80 default_server;
        # listen [::]:80 default_server;

        # SSL configuration
        #
        listen 443 ssl default_server;
        listen [::]:443 ssl default_server;

        ssl_certificate /etc/letsencrypt/live/app.mdiwebma.com/fullchain.pem;
        ssl_certificate_key     /etc/letsencrypt/live/app.mdiwebma.com/privkey.pem;

nginx restart로 동작확인

sudo service nginx restart

 

자동 갱신 
/etc/cron.d/certbot 이 추가되었음 확인가능 


인증서 갱신: 

# 인증성 갱신없이 갱신이 가능한지 체크만 한다.
sudo certbot renew --dry-run

# 실제 인증서를 갱신한다.  80포트가 살아있어야 한다. 
sudo certbot renew

# nginx 재시작해야 적용이 되더라.
sudo service nginx restart

 

자동 갱신하기
HotHandCoding :: certbot를 활용한 https 인증서 발급 및 cron 설정 (tistory.com) 여기를 참고

# 이미 등록되어 있는지 확인
crontab -l

# 없으면 추가
crontab -e

# 2월 28일 마다 (https://crontab.guru/ 참고)
0 0 28 1 * certbot renew --renew-hook "sudo service nginx restart"

 

GCP 우분투에 Mongodb 설치하기

Install MongoDB Community Edition on Ubuntu — MongoDB Manual
여기를 거의 참고했고,  지시에 따라서 하면 거의 되는데 뭔가 잘 안된 부분만 수정한다. 

5.0 버전은 설치가 안 된다.  설치는 -y mongodb-org 로 문서에 되어 있는데 실제로는 에러를 내고 -org 를 지우고 하니 제대로 설치가 되었다. 

sudo apt-get install -y mongodb

서버 실행도  mongod로 실행하니 에러를 내고, mongodb로 하니 제대로 실행되고 mongo를 실행해서 접속도 되었다.

sudo service mongodb start

이 방법은 너무 낮은 버전의 mongodb(2.x)를 설치한다.  삭제하고 다시 시도. (삭제는 메뉴얼대로 하니 문제없이 됨)

 

5.0은 아무리해도 설치가 안 되서, 4.4 버전의 Mongodb를 설치를 다시 시도하기 (메뉴얼에서 해당 OS에 맞는 명령줄을 복사해서 5.0을 4.4로 변경)

echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu xenial/mongodb-org/4.4 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-4.4.list

OS 확인

kimdaejeong@base-backend-1:~$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 16.04.7 LTS
Release:        16.04
Codename:       xenial

64bit인지 확인

kimdaejeong@base-backend-1:~$ uname -m
x86_64

init system 확이

kimdaejeong@base-backend-1:~$ ps --no-headers -o comm 1
systemd


4.4버전의 Mongodb는 설치 성공했음.. 서버 실행
$ sudo systemctl start mongod

Mongo db 5.x의 설치는 상위 버전의 우분투 이미지를 이용해서 설치 시도하여 성공했음 :  https://dajkim76.tistory.com/535

 

GCP에 deno 설치 

Deno - A secure runtime for JavaScript and TypeScript 

curl -fsSL https://deno.land/x/install/install.sh | sh

kimdaejeong@base-backend-1:/etc/cron.d$ curl -fsSL https://deno.land/x/install/install.sh | sh
######################################################################## 100.0%
Archive:  /home/kimdaejeong/.deno/bin/deno.zip
  inflating: /home/kimdaejeong/.deno/bin/deno  
Deno was installed successfully to /home/kimdaejeong/.deno/bin/deno
Manually add the directory to your $HOME/.bash_profile (or similar)
  export DENO_INSTALL="/home/kimdaejeong/.deno"
  export PATH="$DENO_INSTALL/bin:$PATH"
Run '/home/kimdaejeong/.deno/bin/deno --help' to get started

 

위에 표시된 환경변수 두 줄을 .bashrc 맨 아래를 추가해 둔다. 

kimdaejeong@base-backend-1:~$ vi .bashrc
kimdaejeong@base-backend-1:~$ . .bashrc
kimdaejeong@base-backend-1:~$ deno 
Deno 1.12.2
exit using ctrl+d or close()

 

설치단계에서 unzip 에러가 나면 : GitHub - denoland/deno_install: Deno Binary Installer

sudo apt-get install unzip -y

 

Deno Server test

server.ts

import { Application, Router, Context } from "https://deno.land/x/oak/mod.ts"
import { Bson, MongoClient } from "https://deno.land/x/mongo/mod.ts";

// Init mongo db
const client = new MongoClient();

//Connecting to a Local Database
await client.connect("mongodb://localhost:27017");

// Define collection
interface TSList {
    _id: { $oid: string };
    moneyBookId: Bson.ObjectId
    collectionName: string;
    ts: number
  }


const db = client.database("testdb");
const tslist = db.collection<TSList>("TSList");


// Http Handler
const getTSList = async ({ response }: { response: any }) => {
    
    const result = await tslist.find().toArray()
    response.body = {
        code: 0,
        result: result
    }
}

const errorHandler = async (ctx: Context, next: any) => {
    try {
      await next();
    } catch (err) {
      ctx.response.status = 500;
      ctx.response.body = { code: 500, message: err.message }
    }
  }

  const _404 = async (ctx: Context) => {
    ctx.response.status = 404;
    ctx.response.body = { code: 404, message: "404 Not Found!" }
  }

 // Init router
const router = new Router()
router.get("/lt/v1/tslist", getTSList)

// Init server
const app = new Application()
const port = 8000
app.use(errorHandler);
app.use(router.routes())
app.use(router.allowedMethods())
app.use(_404)

console.log("start server port=" + port);
await app.listen({ port: port });

 

서버 실행까지는 성공함
$ deno run --inspect  --allow-net --unstable server.ts
Debugger listening on ws://127.0.0.1:9229/ws/e35c1134-9494-4ee6-b0e5-f2623cbb3e83
start server port=8000

Nginx에 프락시 설정하기
sudo vi /etc/nginx/sites-enabled/default

location /lt {
                proxy_pass http://localhost:8000;
        }

연결 확인 https://app.mdiwebma.com/lt/v1/tslist

GCP에 ssh로 접속하기

메타데이터에서 SSH 키 관리  |  Compute Engine 문서  |  Google Cloud  참고.. 

Mac에 키 생성

ssh-keygen -t rsa -f ~/.ssh/gcp.key -C kimdaejeong

 

GCP 메타 데이타에 public 키 등록 

  $ cat gcp.key.pub

https://console.cloud.google.com/compute/metadata/sshKeys 에 키 등록 

Mac 에서 접속

$ ssh -i ~/.ssh/gcp.key kimdaejeong@<GCP IP>
Enter passphrase for key '/Users/user/.ssh/gcp.key':

OS를 여러개 사용되었을때,  이전 OS에 사용된 IP가 새로 실행한 VM에 다시 할당된 경우는 
~/.ssh/known_hosts  에서 해당 IP를 지우고 다시 시도한다.  

top

posted at

2021. 8. 3. 19:09


POST : Backend study

Mongodb 내장 함수 study

Mongo에 내장 함수를 만들 수 있다고해서 테스트 해 봤다. mongo 쉘에서 테스트해보니 잘 된다.  아마도 nodejs에서는 이 js function을 호출할 수 있는 라이브러리가 있을 것  같다. deno에 mongodb 라이브러리인 https://deno.land/x/mongo  에서는 아직 호출 기능을 제공하지 않는다. mongodb는 trigger도 단독 서버에서는 지원하지 않는다. 관계형 db에서는 trigger를 유용하게 사용했었는데 아쉽다.

 

getNextTS 는 moneyBookId를 별로 sequence 를 증가시켜서 리턴하는 함수다. 

> db.MoneyBook.insertOne({name:"Money book1"})
{
	"acknowledged" : true,
	"insertedId" : ObjectId("610654e943cca4c015817161")
}

> db.MoneyBook.find()
{ "_id" : ObjectId("610654e943cca4c015817161"), "name" : "Money book1" }
> db.TSList.insertOne({moneyBookId: ObjectId("610654e943cca4c015817161"), collectionName: "transfer", ts: 1})
{
	"acknowledged" : true,
	"insertedId" : ObjectId("6106555d43cca4c015817162")
}

> db.TSList.find()
{ "_id" : ObjectId("6106555d43cca4c015817162"), "moneyBookId" : ObjectId("610654e943cca4c015817161"), "collectionName" : "transfer", "ts" : 1 }

# 함수 저장
db.system.js.remove({_id: 'getNextTS'})
db.system.js.insertOne(
    {
        _id : 'getNextTS',
        value : function(moneyBookId, collectionName) {
            var ret = db.TSList.findAndModify( { query: {moneyBookId: moneyBookId, collectionName: collectionName}, update: {$inc: {ts: 1} }, new: true } );
            return ret.ts;
        }
    }
)
> db.loadServerScripts()
> getNextTS(ObjectId("610654e943cca4c015817161"), "transfer")
4

 

Client 앱의 관계형  db의 id는 integer형으로 AUTOINCREMENT 를 이용하면 손쉽게 사용할 수 있다. Mongodb의 id는 이런 형태가 아니기 때문에 코드 구현으로 생성해 주는 방법이 필요하다.  client에도 server id를 저장할 필요가 있어서, type을 Int로 맞출필요가 있다. 

// collectin name
export const kNextId = "next_id"

// id auto increment
export interface INextId {
    did: number
    cid: number,
    id: number
}
import { Bson, MongoClient } from "https://deno.land/x/mongo/mod.ts"

class MyMongoClient {
    dbName: string
    url: string
    client: MongoClient
    
    constructor(dbName: string, url: string) {
        this.dbName = dbName;
        this.url = url;
        this.client = {} as MongoClient;
    }

    async connect() {
        console.log("mongodb connecting ...")
        const client = new MongoClient();
        await client.connect(this.url);
        this.client = client;
        console.log("mongodb connected OK")
    }
    
    getDatabase() {
        return this.client.database(this.dbName);
    }
}


export const mongoClient = new MyMongoClient("testdb", "mongodb://127.0.0.1:27017")
await mongoClient.connect()

const db = mongoClient.getDatabase()
const nextIdCollection = db.collection<INextId>(kNextId)
export async function getNextId(cid: number, did: number) : Promise<INextId | undefined> {
    return await nextIdCollection.findAndModify(
        {cid: cid, did: did},
        { 
          update: { $inc: { id: 1 } },
          new: true, 
          upsert: true 
        }
    )
}

 

이렇게 사용한다. 서버에는 사용자 별로 did가 생성되는데, 특정 collection에  특정  did 별로 sequence를 발급하는 함수를 직접 호출해서 구하고, insert 하기 전에 그 값을 사용하도록 한다. 

const aid = (await getNextId(kCidAccount, did))!.id

 

top

posted at

2021. 8. 1. 17:42


POST : Backend study

Deno api server by Nginx (Study log)

Test Api server를 우선 만들어보았다. 

"deno Restful api "로 검색했을때 가장 많이 사용된 라이브러리인 oak를 이용하기로 했다.  간단히 8080 port에 http 서버를 띄우고, 어떤 요청이 오더라도  아래 json 문자열을 응답한다. 

{"code":1,"message":"Service is maintenance mode."}

 

maintenance.ts

import { Application } from "https://deno.land/x/oak/mod.ts"

const app = new Application()

// 8080 port is maintenance service 
const port = 8080

// response body
const body =  {
  code: 1,
  message: "Service is maintenance mode."
}

app.use((context) => {
  context.response.body = body;
})

console.log("start server with maintenance mode, port=" +  port)
await app.listen({ port: port})

 

서버 실행

$ deno run --allow-net maintenance.ts &                                                                                                                                                 ✔  102  16:51:02
[1] 92626
start server with maintenance mode, port=8080

브라우저를 띄우고  http://localhost:8080 을 입력해서 동작을 확인한다. 

 

Application server를 만들어 보자.

지난번에 만들었던 test.ts에 웹 서버기능을 추가했다. 

server.ts

import { Application, Router, Context } from "https://deno.land/x/oak/mod.ts"
import { Bson, MongoClient } from "https://deno.land/x/mongo/mod.ts";

// Init mongo db
const client = new MongoClient();

//Connecting to a Local Database
await client.connect("mongodb://localhost:27017");

// Define collection
interface TSList {
    _id: { $oid: string };
    id: number
    name: string;
  }


const db = client.database("testdb");
const tslist = db.collection<TSList>("tslist");


// Http Handler
const getTSList = async ({ response }: { response: any }) => {
    const result = await tslist.find().toArray()
    response.body = {
        code: 0,
        result: result
    }
}

const errorHandler = async (ctx: Context, next: any) => {
    try {
      await next();
    } catch (err) {
      ctx.response.status = 500;
      ctx.response.body = { code: 500, message: err.message }
    }
  }

  const _404 = async (ctx: Context) => {
    ctx.response.status = 404;
    ctx.response.body = { code: 404, message: "404 Not Found!" }
  }

 // Init router
const router = new Router()
router.get("/tslist", getTSList)

// Init server
const app = new Application()
const port = 8000
app.use(errorHandler);
app.use(router.routes())
app.use(router.allowedMethods())
app.use(_404)

console.log("start server port=" + port);
await app.listen({ port: port });

 

서버 실행 

$ deno run --allow-net --unstable server.ts
Check file:///Users/user/djkim/test/server.ts
start server port=8000

 

웹 브라우저에 http://localhost:8000/tslist 를 입력하면 아래와 같이 응답한다.

{"code":0,"result":[{"_id":"6103b0bfcddd2d8fe3850ee6","id":1,"name":"user"}]}

 

Mac에 Nginx 설치하기

Nginx는 api 게이트웨이 서버이고, 여러 static 파일이나 zip 파일을 호스팅하는 용도이다.
간단히 설치하기

brew install nginx

 

서비스 Port 80으로 변경하기

vi /usr/local/etc/nginx/nginx.conf

 

서비스 시작하기

brew services start nginx

서버 동작 확인하기  http://localhost

webroot 확인

$ nginx -V
nginx version: nginx/1.21.1
built by clang 12.0.0 (clang-1200.0.32.29)
built with OpenSSL 1.1.1k  25 Mar 2021
TLS SNI support enabled
configure arguments: --prefix=/usr/local/Cellar/nginx/1.21.1

 

Deno 서버 여러개 띄우고 로드밸런싱해보기

더보기

upstream deno_server {

        server localhost:8000 weight=2;
        server localhost:8080 weight=1;
    }

    server {
        listen       80;
        server_name  localhost;

        #charset koi8-r;

        #access_log  logs/host.access.log  main;

        location / {
            #root   html;
            #index  index.html index.htm;
            proxy_pass http://deno_server;
        }

http://nginx.org/en/docs/http/load_balancing.html 참고

서버 reload (restart와 다른가?)
$ brew services reload  nginx

브라우저에서 http://localhost/tslist 라고 입력하면 
1/3의 요청은 포트 8080으로 가고 , 나머지는 8000으로 간다.  서버가 버전업이 되었을 때 weight를 조정하여 5%의 사용자는 새로운 서버로 동작시켜서 문제가 없는지 확인한 후에 점진적으로 서버 배포가 가능하도록 할 수 있다. 

proxy_pass http://locahost:8080;
으로 변경한다면, 서버가 패치하는 동안 모든 요청을 유지 보수 모드로 서버 상태를 변경할 수 있다. 

아니면 nginx를 이용해서 처리도 가능하다.

# 유지보수일때 json을 그냥 리턴시킨다.
location / {
            #root   html;
            #index  index.html index.htm;
            #proxy_pass http://localhost.com:8000;
            return 200 "{\"code\":1, \"message\":\"service is on maintenance mode\"}";
            add_header Content-Type text/plain;
        }

 

location / {   이 부분을 적절히 변겨하면 포트마다 여러 서비스 서버를 띄우고 각각 다른 서버 인스턴스로 프락시 할 수 있다. 
localhost/blog
localhost/wiki
localhost/api

 

Nginx에 https 적용하기 #1

[Nginx] Nginx HTTPS 및 Let's Encryt SSL 인증서 적용⋆ JackerLab 를 참고했다. 
설치

$ brew install certbot

 

내 mac에 도메인 할당하기.  인증서에는 .(dot)이 최소 하나가 들어있는 도메인이 필요하다. 따라서 localhost.com 을 127.0.0.1로 지정한다. 

$ sudo vi /etc/hosts

127.0.0.1       localhost.com
을 추가한다.   추가하고나면  http://localhost.com/tslist 로 접속이 잘 된다. 

인증서 발급: 도메인은  임시로 localhost 로 지정한다. 하기 전에 nginx를 먼저 중지해야 한다. 

$ brew services stop  nginx
$ sudo certbot certonly --manual

몇 가지 확인을 해 본 결과 인증서 발급 서버에서 localhost.com으로 접속을 시도해서 정상적인 서버인지 확인하는 과정이 있는데 localhost.com은 정상적인 dns 서버에 등록된 도메인이 아니기 때문에 결국 연결 실패하게 된다.

[centos7] HTTPS 무료 인증설치방법(--manual 옵션 사용) (tistory.com) 참고 

 

Nginx에 https 적용하기 (OpenSSL) #2

파란크리스마스 :: Apache 2.4 : OpenSSL 테스트인증서 생성, https 설정 (tistory.com) 여기를 참고했다.

 server {
        listen       443 ssl;
        server_name  localhost.com;

        ssl_certificate      /var/www/letsencrypt/private.crt;
        ssl_certificate_key  /var/www/letsencrypt/private.key;

        ssl_protocols TLSv1.2;
        #ssl_session_cache    shared:SSL:1m;
        #ssl_session_timeout  5m;

        #ssl_ciphers  HIGH:!aNULL:!MD5;
        #ssl_prefer_server_ciphers  on;

        location / {
            #root   html;
            #index  index.html index.htm;
            proxy_pass http://deno_server;
        }
    }

이렇게 설정했고(letsencrypt는 이전 테스트에서 사용하던 디렉토리를 그대로 이용한 것), 브라우저에서 안전하지 않은 인증서라고 하면서 연결을 거부한다. 크롬에서 이제는 test 인증서는 연결을 거부하는 것 같다. 예전에는 됬을 수도 있다.  결국 공인 IP를 가진 서버가 필요하고 도메인 연결도 필요하다. 그런경우 certbot을 이용해서 가능할 듯 하다.

top

posted at

2021. 7. 31. 17:11


POST : Backend study

Mongo db / Deno / VisualStudio code 설치 (Study log)

모바일 앱의 싱크 서버가 필요해서, 백엔드를 구성하는 법을 공부하는 중. 

DB는 Mongo db를 사용하기로 결정했고,  서버의 주 작성 언어를 자바스크립트(node, deno)로 할지, 코틀린(spring boot)으로 할지에 따라서 프레임웍이 달라지는데 둘 다 공부해 보기로 했다.  일단 자바 스크립트로 하는 공부를 했고, Node로 할까하다가 Deno로 하고 여러가지 공부 로그를 남긴다. 

 

1. Mac에서 Mongo db 설치

GitHub - mongodb/homebrew-brew: The Official MongoDB Software Homebrew Tap

 

GitHub - mongodb/homebrew-brew: The Official MongoDB Software Homebrew Tap

The Official MongoDB Software Homebrew Tap. Contribute to mongodb/homebrew-brew development by creating an account on GitHub.

github.com

 

준비
$ brew update

설치
$ brew tap mongodb/brew

설치
$ brew install mongodb-community

버전 확인
$ mongod --version
db version v5.0.1
Build Info: {
    "version": "5.0.1",
    "gitVersion": "318fd9cabc59dc9651f3189b622af6e06ab6cd33",
    "modules": [],
    "allocator": "system",
    "environment": {
        "distarch": "x86_64",
        "target_arch": "x86_64"
    }
}

서버 시작
$ brew services start mongodb-community

쉘 접속
$ mongo

2. Deno 설치

Deno - A secure runtime for JavaScript and TypeScript

 

Deno - A secure runtime for JavaScript and TypeScript

Deno is a simple, modern and secure runtime for JavaScript and TypeScript that uses V8 and is built in Rust.

deno.land

$ brew install deno

$ deno --version
deno 1.12.2 (release, x86_64-apple-darwin)
v8 9.2.230.14
typescript 4.3.5

 

3. VisualStuiod Code 설치

Documentation for Visual Studio Code

 

Documentation for Visual Studio Code

Find out how to set-up and get the most from Visual Studio Code. Optimized for building and debugging modern web and cloud applications. Visual Studio Code is free and available on your favorite platform - Linux, macOS, and Windows.

code.visualstudio.com

deno plugin 설치

deno로 검색하여, 플러그인  id: denoland.vscode-deno  게시자: denoland 인 플러그인 설치.

 

4. Deno에서 Mongo db 접속하기

mongo@v0.24.0 | Deno 를 이용하기 (mongodb를 사용할 수 있는 여러 라이브러리가 있지만 스타가 많고 간단해보임)

에러 핸들링

Top-level 'await' expressions are only allowed when the 'module' option is set to 'esnext' or 'system', and the 'target' option is set to 'es2017' or higher.ts(1378)

>> Deno: VS Code typescript doesnt know ES6 - Stack Overflow 

tsconfig.json 작성하여 아래처럼 저장

{
    "compilerOptions": {
      "module": "ESNext",
      "target": "ES2020"
    }
  }

test.ts 작성

import { Bson, MongoClient } from "https://deno.land/x/mongo/mod.ts";

const client = new MongoClient();

//Connecting to a Local Database
await client.connect("mongodb://localhost:27017");

// Define collection
interface TSList {
    _id: { $oid: string };
    id: number
    name: string;
  }


const db = client.database("testdb");
const tslist = db.collection<TSList>("tslist");
const allTsList = await tslist.find().toArray()

console.log(allTsList)

실행하기

$ deno run --allow-net --unstable test.ts
[
  {
    _id: ObjectId2 {
      [Symbol(id)]: Buffer2(12) [
         97,   3, 176, 191, 205,
        221,  45, 143, 227, 133,
         14, 230
      ]
    },
    id: 1,
    name: "user"
  }
]

mongo 쉘에서 입력해 놓은 데이타를 제대로 불러왔다.

 

5. 파일 한개로 실행가능한 배포 파일 만들기

deno가 파일 한개로 배포가 가능하다고 하는데 가능한지 알아봤다. deno compile로 test라는 파일을 만들었는데 무려 72메가 용량이고, 실행하니 제대로 동작한다. deno가 설치되지 않은 곳에서도 실행이 되는지는 잘 모르겠다.

Compiling executables | Manual | Deno

$ deno compile  --allow-net --unstable test.ts
Check file:///Users/user/djkim/test/test.ts
Bundle file:///Users/user/djkim/test/test.ts
Compile file:///Users/user/djkim/test/test.ts
Emit test

$./test
[
  {
    _id: ObjectId2 {
      [Symbol(id)]: Buffer2(12) [
         97,   3, 176, 191, 205,
        221,  45, 143, 227, 133,
         14, 230
      ]
    },
    id: 1,
    name: "user"
  }
]

top

posted at

2021. 7. 30. 17:32


POST : Android Dev Study

Mac에서 안드로이드 휴대폰 미러링하기 (scrcpy 유틸리티)

top

posted at

2021. 2. 23. 14:55


POST : Android Dev Study

DataBinding ViewHolder 새로 만들지 않고 사용하기

코드를 중복시키는 것을 싫어해서 예전에 RecyclerViewAdapter도 하나의 구현 코드만 사용하고 ViewHolderCreator interface를 이용해서 ViewHolder만 새로 만들어사용했는데 DataBinding을 이용하면 ViewHolder도 만들지 않아도 될 것 같아서 이렇게 샘플 코드를 작성해 보았다. 

설치된 app 목록을 보여주고, 선택된 앱 목록을 저장하고, 이름으로 필터하는 간단한 프로그램인데 전체 코드는 아래와 같다. 

AppListActivity

class AppListActivity : BaseAppCompatActivity(R.layout.app_list, R.menu.app_list) {

    private val adapter: DataBindingRecyclerAdapter = DataBindingRecyclerAdapter(ViewModel())
    private val recyclerView: RecyclerView by findView(R.id.recycler_view)
    private val progressBarView: View by findView(R.id.progress_bar)
    private val selectedApps = HashSet<String>()
    private lateinit var appItemList: List<AppItem>
    private var searchWord: String? = null

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        recyclerView.layoutManager = LinearLayoutManager(context)
        recyclerView.addItemDecoration(DividerItemDecoration(context, LinearLayoutManager.VERTICAL))
        recyclerView.adapter = adapter
        Settings.pushNotiApps.value.split('|').filter { it.isNotEmpty() }.forEach {
            selectedApps += it
        }
        loadList()
    }

    private fun loadList() = lifecycleScope.launch {
        progressBarView.isVisible = true
        appItemList = withContext(Dispatchers.IO) {
            val pm = packageManager
            pm.getInstalledApplications(PackageManager.GET_META_DATA)
                .filter { (it.flags and ApplicationInfo.FLAG_SYSTEM) == 0 } // except system app
                .map { applicationInfo ->
                    AppItem(
                        applicationInfo.loadIcon(pm),
                        applicationInfo.loadLabel(pm).toString(),
                        applicationInfo.packageName,
                        selectedApps.contains(applicationInfo.packageName)
                    )
                }
                .sortedWith(compareBy({ !it.selected }, { it.name })) // order by selected, name
                .toList()
        }
        adapter.clear()
        adapter.addAll(appItemList)
        progressBarView.isVisible = false
    }

    @OnMenuClick(R.id.menu_save)
    fun onClickSave(menuItem: MenuItem) {
        if (!::appItemList.isInitialized) return
        appItemList
            .filter { it.selected }
            .joinToString("|") { it.packageName }
            .also {
                Settings.pushNotiApps.value = it
            }
        finish()
        Utils.startNotiListenService(this)
    }

    @OnMenuClick(R.id.menu_search)
    fun onClickSearch(menuItem: MenuItem) {
        if (!::appItemList.isInitialized) return
        InputDialogBuilder(this)
            .setMaxLines(1)
            .setTitle(R.string.search)
            .setValue(searchWord)
            .setValueCanEmpty(true)
            .setCallback2 { dialog, value ->
                searchWord = value.takeIf { it.isNotEmpty() }
                appItemList
                    .filter { appItem ->
                        searchWord?.let { appItem.name.contains(it, ignoreCase = true) } ?: true
                    }
                    .also {
                        adapter.clear()
                        adapter.addAll(it)
                    }
                dialog.dismiss()
            }
            .show()
    }

    class AppItem(val icon: Drawable, val name: String, val packageName: String, var selected: Boolean) :
        RecyclerViewItem {

        val backgroundColor: Int
            get() {
                return if (selected) Color.GREEN else Color.TRANSPARENT
            }

        override fun getLayoutId(): Int = R.layout.app_list_item
    }

    inner class ViewModel : DataBindingViewModel {

        fun onClick(item: AppItem) {
            item.selected = !item.selected
            adapter.notifyItemChanged(adapter.getPosition(item))
        }
    }
}

R.layout.app_list

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <ProgressBar
        android:id="@+id/progress_bar"
        style="?android:attr/progressBarStyleLarge"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

R.layout.app_list_item

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>

        <variable
            name="vm"
            type="com.mdiwebma.leetzsche.activity.noti.AppListActivity.ViewModel" />

        <variable
            name="item"
            type="com.mdiwebma.leetzsche.activity.noti.AppListActivity.AppItem" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:background="@{item.backgroundColor}"
        android:onClick="@{(v) -> vm.onClick(item)}">

        <ImageView
            android:id="@+id/icon"
            android:layout_width="30dp"
            android:layout_height="30dp"
            android:layout_marginLeft="10dp"
            android:src="@{item.icon}"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <TextView
            android:id="@+id/name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="10dp"
            android:layout_marginTop="3dp"
            android:text="@{item.name}"
            app:layout_constraintStart_toEndOf="@+id/icon"
            app:layout_constraintTop_toTopOf="parent" />

        <TextView
            android:id="@+id/package_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="10dp"
            android:layout_marginTop="3dp"
            android:text="@{item.packageName}"
            app:layout_constraintStart_toEndOf="@+id/icon"
            app:layout_constraintTop_toBottomOf="@+id/name" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

 

실제로 구현한 것은 AppItem과 ViewModel이 전부고, 나머지 코드는 공용 코드라고 생각하면 된다. 다른 페이지나 프래그먼트에서 수정없이 재사용되는 코드이다. 

공용코드

interface DataBindingViewModel

interface RecyclerViewItem {

    fun getLayoutId(): Int
}

open class DataBindingViewHolder(private val binding: ViewDataBinding) : RecyclerView.ViewHolder(binding.root) {

    fun onBind(vm: DataBindingViewModel?, item: RecyclerViewItem) {
        if (vm != null) binding.setVariable(BR.vm, vm)
        binding.setVariable(BR.item, item)
        binding.executePendingBindings()
    }
}

class DataBindingRecyclerAdapter(
    private val viewModel: DataBindingViewModel? = null
) : RecyclerView.Adapter<DataBindingViewHolder>() {

    private val itemList = mutableListOf<RecyclerViewItem>()
    private var notifyOnChange: Boolean = true
    private val lock = Any()

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DataBindingViewHolder {
        return DataBindingViewHolder(
            DataBindingUtil.inflate(
                LayoutInflater.from(parent.context),
                viewType,
                parent,
                false
            )
        )
    }

    override fun getItemCount(): Int = itemList.size

    override fun getItemViewType(position: Int): Int = itemList[position].getLayoutId()

    override fun onBindViewHolder(holder: DataBindingViewHolder, position: Int) =
        holder.onBind(viewModel, itemList[position])

    fun getItemList(): List<RecyclerViewItem> = itemList

    fun getPosition(item: RecyclerViewItem): Int = itemList.indexOf(item)

    fun add(item: RecyclerViewItem) {
        synchronized(lock) {
            itemList.add(item)
        }
        if (notifyOnChange) notifyDataSetChanged()
    }

    fun add(index: Int, item: RecyclerViewItem) {
        synchronized(lock) {
            itemList.add(index, item)
        }
        if (notifyOnChange) notifyDataSetChanged()
    }

    fun add(vararg items: RecyclerViewItem) {
        synchronized(lock) {
            itemList.addAll(items)
        }
        if (notifyOnChange) notifyDataSetChanged()
    }

    fun addAll(list: Collection<RecyclerViewItem>) {
        synchronized(lock) {
            itemList.addAll(list)
        }
        if (notifyOnChange) notifyDataSetChanged()
    }

    fun remove(item: RecyclerViewItem) {
        synchronized(lock) {
            itemList.remove(item)
        }
        if (notifyOnChange) notifyDataSetChanged()
    }

    fun clear() {
        synchronized(lock) {
            itemList.clear()
        }
        if (notifyOnChange) notifyDataSetChanged()
    }

    fun setNotifyOnChange(notifyOnChange: Boolean) {
        this.notifyOnChange = notifyOnChange
    }
}

 

여기서 좀 더 보강한다면 ViewHolderCreator를 제공한다던지, 더 보기 로딩뷰 같은 것을 구현하면 된다. 

interface DataBindingViewHolderCreator {

    fun createViewHolder(parent: ViewGroup, viewType: Int): DataBindingViewHolder
}

 

top

posted at

2020. 8. 2. 02:57


CONTENTS

Seize the day
BLOG main image
김대정의 앱 개발 노트와 사는 이야기
RSS 2.0Tattertools
공지
아카이브
최근 글 최근 댓글
카테고리 태그 구름사이트 링크