Seize the day

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

POST : Android Dev Study

이미지를 pdf로 저장하기

참고

https://www.baeldung.com/java-pdf-creation 

itext 를 이용하는 방법

라이센스 문제가 있다. 상업적 이용은 라이센스를 구매하거나, 구매하지 않는다면  내 소스코드를 공개해야한다.  (AGPL)

    implementation 'com.itextpdf:itextg:5.5.10' // images to pdf
	// or     implementation 'com.itextpdf:itextpdf:5.5.13.4' // images to pdf
import com.itextpdf.text.Document
import com.itextpdf.text.Image
import com.itextpdf.text.PageSize
import com.itextpdf.text.pdf.PdfWriter

import java.io.ByteArrayOutputStream
import java.io.FileInputStream

// itextpdf
    fun writeImagesForItext(uriStrList: List<String>, outputUri: Uri): Int {
        var count: Int = 0
        try {
            val document = Document(PageSize.A4, 15f, 15f, 15f, 15f)
            val pdfWriter = PdfWriter.getInstance(document, contentResolver.openOutputStream(outputUri))
            document.open()
            val stream = ByteArrayOutputStream()
            for (uriStr in uriStrList) {
                try {
                    stream.reset() // for reuse
                    val uri = Uri.parse(uriStr)
                    val bitmap: Bitmap = if (uri.scheme != "content") {
                        BitmapFactory.decodeStream(FileInputStream(uriStr))
                    } else {
                        contentResolver.openInputStream(uri).use {
                            BitmapFactory.decodeStream(it)
                        }
                    }

                    var quality = Settings.writePdfImageQuality.value
                    if (quality < 5) quality = 5
                    if (quality > 100) quality = 100
                    bitmap.compress(Bitmap.CompressFormat.JPEG, quality, stream)
                    if (true) {
                        val image = Image.getInstance(stream.toByteArray())
                        document.setPageSize(image)
                        document.newPage()
                        image.setAbsolutePosition(0f, 0f)
                        document.add(image)
                    }
                    bitmap.recycle()
                    count++
                } catch (ex: Exception) {
                    if (BuildConfig.DEBUG) {
                        DUtils.alert(ex.toString())
                    }
                }
            }

            // close
            document.close()
            pdfWriter?.close()
        } catch (ex: Exception) {
            if (BuildConfig.DEBUG) {
                DUtils.alert(ex.toString())
            }
        }
        return count
    }

 

Apache pdfbox를 이용하는 방법

https://pdfbox.apache.org/download.html 를 이용할 수 있지만  Android에서는 바로 사용할 수 없다. Android으로 랩핑한 라이브러리를 이용한다. https://github.com/TomRoush/PdfBox-Android 

implementation 'com.tom-roush:pdfbox-android:2.0.27.0'
import com.tom_roush.pdfbox.pdmodel.PDDocument
import com.tom_roush.pdfbox.pdmodel.PDPage
import com.tom_roush.pdfbox.pdmodel.PDPageContentStream
import com.tom_roush.pdfbox.pdmodel.common.PDRectangle
import com.tom_roush.pdfbox.pdmodel.graphics.image.PDImageXObject



// apache pdfbox
    fun writeImagesForPdfBox(uriStrList: List<String>, outputUri: Uri): Int {
        var count: Int = 0
        try {
            val document = PDDocument()
            val stream = ByteArrayOutputStream()
            for (uriStr in uriStrList) {
                try {
                    stream.reset() // for reuse
                    val uri = Uri.parse(uriStr)
                    val bitmap: Bitmap = if (uri.scheme != "content") {
                        BitmapFactory.decodeStream(FileInputStream(uriStr))
                    } else {
                        contentResolver.openInputStream(uri).use {
                            BitmapFactory.decodeStream(it)
                        }
                    }

                    var quality = Settings.writePdfImageQuality.value
                    if (quality < 5) quality = 5
                    if (quality > 100) quality = 100
                    bitmap.compress(Bitmap.CompressFormat.JPEG, quality, stream)
                    if (true) {
                        val page = PDPage(PDRectangle(bitmap.width.toFloat(), bitmap.height.toFloat()))
                        document.addPage(page)
                        val img = PDImageXObject.createFromByteArray(document, stream.toByteArray(), "test")
                        val contentStream = PDPageContentStream(document, page)
                        contentStream.drawImage(img, 0f, 0f)
                        contentStream.close()
                    }
                    bitmap.recycle()
                    count++
                } catch (ex: Exception) {
                    if (BuildConfig.DEBUG) {
                        DUtils.alert(ex.toString())
                    }
                }
            }

            document.save(contentResolver.openOutputStream(outputUri));
            document.close();
        } catch (ex: Exception) {
            if (BuildConfig.DEBUG) {
                DUtils.alert(ex.toString())
            }
        }
        return count
    }

 

top

posted at

2024. 6. 19. 18:56


POST : Android Dev Study

Room DB 백업 및 복구

Room db  생성할 때 속성을 별도로 지정하지 않으면 -shm, -wal 이 붙은 파일이 같이 생성된다. 여기에 대한 정보를 모아보면..  

https://androidexplained.github.io/android/room/2020/10/03/room-backup-restore.html  를
https://smparkworld.com/blog/detail/26  를
https://www.sqlite.org/wal.html 를
https://www.sqlite.org/c3ref/wal_checkpoint_v2.html 를 참고했다.   

JournalMode.TRUNCATE로 생성하면 깔끔하게 하나의 db 파일로 관리된다.  하지만 그냥 기본값으로 지정하면 디바이스 메모리를 고려하여 더 낫게 동작하므로 강제로 TRUNCATE로 지정하지 않고 3개 파일 모두를 백업하도록 하겠다. 

Room.databaseBuilder(context, AppDatabase::class.java, name)
    .setJournalMode(RoomDatabase.JournalMode.TRUNCATE)
    .build()

 

백업하기 전에  wal_checkpoint를 실행해서 혹시 모를 정보도 모두 db 파일에 저장하도록 한다. 

interface UserDao {
    @RawQuery
    fun checkpoint(supportSQLiteQuery: SupportSQLiteQuery?): Single<Int>
}
userDao.checkpoint((SimpleSQLiteQuery("pragma wal_checkpoint(full)")))

 

실제로 백업을 실행하기 직전에 DB 연결을 close해야 안전할 듯.

AppDatabase.getInstance(activity).close()

 

DB 파일만 필요한 것이 아니라 다른 여러 정보가 필요하므로 meta.json 파일에 저장하여 같이 백업할 필요가 있다. 따라서 결국에는 zip 파일 형태로 백업을 하고, 복구도 zip 파일에서 하여야 한다. 

https://injunech.tistory.com/402  zip 파일 생성과 풀기는 여기를 참고했다. 

https://github.com/rafi0101/Android-Room-Database-Backup/blob/master/core/src/main/java/de/raphaelebner/roomdatabasebackup/core/RoomBackup.kt 
https://fastbikri.com/blog/implementing-backup-and-restore-feature-of-sqlite-database-to-comply-with-scoped-storage-android-11/  백업과 복구는 여기를 참고했다. 

 

top

posted at

2024. 6. 8. 23:06


POST : Backend study

Linux 명령어 공부

서버 하드웨어 조사

/proc/cpuinfo
/proc/version
uname -a

ec2-user@Azure-Backend1:~$ cat /proc/cpuinfo
processor	: 0
vendor_id	: GenuineIntel
cpu family	: 6
model		: 79
model name	: Intel(R) Xeon(R) CPU E5-2673 v4 @ 2.30GHz
stepping	: 1
microcode	: 0xffffffff
cpu MHz		: 2294.687
cache size	: 51200 KB
physical id	: 0
siblings	: 1
core id		: 0
cpu cores	: 1
apicid		: 0
initial apicid	: 0
fpu		: yes
fpu_exception	: yes
cpuid level	: 20
wp		: yes
flags		: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss syscall nx pdpe1gb rdtscp lm constant_tsc rep_good nopl xtopology cpuid pni pclmulqdq ssse3 fma cx16 pcid sse4_1 sse4_2 movbe popcnt aes xsave avx f16c rdrand hypervisor lahf_lm abm 3dnowprefetch invpcid_single pti fsgsbase bmi1 hle avx2 smep bmi2 erms invpcid rtm rdseed adx smap xsaveopt arch_capabilities
bugs		: cpu_meltdown spectre_v1 spectre_v2 spec_store_bypass l1tf mds swapgs taa mmio_stale_data bhi
bogomips	: 4589.37
clflush size	: 64
cache_alignment	: 64
address sizes	: 46 bits physical, 48 bits virtual
power management:

ec2-user@Azure-Backend1:~$ uname -a
Linux Azure-Backend1 6.5.0-1021-azure #22~22.04.1-Ubuntu SMP Tue Apr 30 16:08:18 UTC 2024 x86_64 x86_64 x86_64 GNU/Linux
ec2-user@Azure-Backend1:~$ cat /proc/version
Linux version 6.5.0-1021-azure (buildd@lcy02-amd64-097) (x86_64-linux-gnu-gcc-11 (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0, GNU ld (GNU Binutils for Ubuntu) 2.38) #22~22.04.1-Ubuntu SMP Tue Apr 30 16:08:18 UTC 2024

 

lscpu

ec2-user@Azure-Backend1:~$ lscpu
Architecture:            x86_64
  CPU op-mode(s):        32-bit, 64-bit
  Address sizes:         46 bits physical, 48 bits virtual
  Byte Order:            Little Endian
CPU(s):                  1
  On-line CPU(s) list:   0
Vendor ID:               GenuineIntel
  Model name:            Intel(R) Xeon(R) CPU E5-2673 v4 @ 2.30GHz
    CPU family:          6
    Model:               79
    Thread(s) per core:  1
    Core(s) per socket:  1
    Socket(s):           1
    Stepping:            1
    BogoMIPS:            4589.37
    Flags:               fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss syscall nx pdpe1gb rdtscp lm constant_tsc rep_good nopl xtopology cpuid pni pclmulqdq ssse3 fma cx16 pcid sse4_1 sse4_2 mov
                         be popcnt aes xsave avx f16c rdrand hypervisor lahf_lm abm 3dnowprefetch invpcid_single pti fsgsbase bmi1 hle avx2 smep bmi2 erms invpcid rtm rdseed adx smap xsaveopt arch_capabilities
Virtualization features:
  Hypervisor vendor:     Microsoft
  Virtualization type:   full
Caches (sum of all):
  L1d:                   32 KiB (1 instance)
  L1i:                   32 KiB (1 instance)
  L2:                    256 KiB (1 instance)
  L3:                    50 MiB (1 instance)
NUMA:
  NUMA node(s):          1
  NUMA node0 CPU(s):     0
Vulnerabilities:
  Gather data sampling:  Not affected
  Itlb multihit:         Not affected
  L1tf:                  Mitigation; PTE Inversion
  Mds:                   Vulnerable: Clear CPU buffers attempted, no microcode; SMT Host state unknown
  Meltdown:              Mitigation; PTI
  Mmio stale data:       Vulnerable: Clear CPU buffers attempted, no microcode; SMT Host state unknown
  Retbleed:              Not affected
  Spec rstack overflow:  Not affected
  Spec store bypass:     Vulnerable
  Spectre v1:            Mitigation; usercopy/swapgs barriers and __user pointer sanitization
  Spectre v2:            Mitigation; Retpolines; STIBP disabled; RSB filling; PBRSB-eIBRS Not affected; BHI Retpoline
  Srbds:                 Not affected
  Tsx async abort:       Vulnerable: Clear CPU buffers attempted, no microcode; SMT Host state unknown

 

/proc/meminfo

ec2-user@Azure-Backend1:~$ cat /proc/meminfo
MemTotal:         853544 kB
MemFree:           72912 kB
MemAvailable:      91352 kB
Buffers:            2316 kB
Cached:           136556 kB
SwapCached:            0 kB
Active:           351704 kB
Inactive:         277860 kB
Active(anon):     262056 kB
Inactive(anon):   241856 kB
Active(file):      89648 kB
Inactive(file):    36004 kB
Unevictable:       30724 kB
Mlocked:           27652 kB
SwapTotal:             0 kB
SwapFree:              0 kB
Zswap:                 0 kB
Zswapped:              0 kB
Dirty:                60 kB
Writeback:             0 kB
AnonPages:        521452 kB
Mapped:            75128 kB
Shmem:              4156 kB
KReclaimable:      23504 kB
Slab:              67192 kB
SReclaimable:      23504 kB
SUnreclaim:        43688 kB
KernelStack:        3260 kB
PageTables:         4396 kB
SecPageTables:         0 kB
NFS_Unstable:          0 kB
Bounce:                0 kB
WritebackTmp:          0 kB
CommitLimit:      426772 kB
Committed_AS:    1361764 kB
VmallocTotal:   34359738367 kB
VmallocUsed:       36136 kB
VmallocChunk:          0 kB
Percpu:              500 kB
HardwareCorrupted:     0 kB
AnonHugePages:    264192 kB
ShmemHugePages:        0 kB
ShmemPmdMapped:        0 kB
FileHugePages:         0 kB
FilePmdMapped:         0 kB
Unaccepted:            0 kB
HugePages_Total:       0
HugePages_Free:        0
HugePages_Rsvd:        0
HugePages_Surp:        0
Hugepagesize:       2048 kB
Hugetlb:               0 kB
DirectMap4k:       81120 kB
DirectMap2M:      915456 kB
DirectMap1G:           0 kB

 

/sys/devices

ec2-user@Azure-Backend1:~$ ls -al /sys/devices
total 0
drwxr-xr-x 15 root root 0 Jun  5 19:33 .
dr-xr-xr-x 12 root root 0 Jun  5 19:33 ..
drwxr-xr-x  4 root root 0 Jun  6 08:08 0006:045E:0621.0001
drwxr-xr-x  6 root root 0 Jun  5 19:33 LNXSYSTM:00
drwxr-xr-x  3 root root 0 Jun  6 08:08 breakpoint
drwxr-xr-x  3 root root 0 Jun  6 08:08 isa
drwxr-xr-x  4 root root 0 Jun  6 08:08 kprobe
drwxr-xr-x  5 root root 0 Jun  6 08:08 msr
drwxr-xr-x 12 root root 0 Jun  6 08:00 platform
drwxr-xr-x  4 root root 0 Jun  6 08:08 pnp0
drwxr-xr-x  3 root root 0 Jun  6 08:08 software
drwxr-xr-x 10 root root 0 Jun  5 19:33 system
drwxr-xr-x  3 root root 0 Jun  6 08:08 tracepoint
drwxr-xr-x  4 root root 0 Jun  6 08:08 uprobe
drwxr-xr-x 20 root root 0 Jun  5 19:33 virtual

 

mount

ec2-user@Azure-Backend1:~$ mount
/dev/sda1 on / type ext4 (rw,relatime,discard,errors=remount-ro)
devtmpfs on /dev type devtmpfs (rw,nosuid,noexec,relatime,size=423640k,nr_inodes=105910,mode=755,inode64)
proc on /proc type proc (rw,nosuid,nodev,noexec,relatime)
sysfs on /sys type sysfs (rw,nosuid,nodev,noexec,relatime)
securityfs on /sys/kernel/security type securityfs (rw,nosuid,nodev,noexec,relatime)
tmpfs on /dev/shm type tmpfs (rw,nosuid,nodev,inode64)
devpts on /dev/pts type devpts (rw,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=000)
tmpfs on /run type tmpfs (rw,nosuid,nodev,size=170712k,nr_inodes=819200,mode=755,inode64)
tmpfs on /run/lock type tmpfs (rw,nosuid,nodev,noexec,relatime,size=5120k,inode64)
cgroup2 on /sys/fs/cgroup type cgroup2 (rw,nosuid,nodev,noexec,relatime,nsdelegate,memory_recursiveprot)
pstore on /sys/fs/pstore type pstore (rw,nosuid,nodev,noexec,relatime)
efivarfs on /sys/firmware/efi/efivars type efivarfs (rw,nosuid,nodev,noexec,relatime)
bpf on /sys/fs/bpf type bpf (rw,nosuid,nodev,noexec,relatime,mode=700)
systemd-1 on /proc/sys/fs/binfmt_misc type autofs (rw,relatime,fd=29,pgrp=1,timeout=0,minproto=5,maxproto=5,direct,pipe_ino=14348)
hugetlbfs on /dev/hugepages type hugetlbfs (rw,relatime,pagesize=2M)
mqueue on /dev/mqueue type mqueue (rw,nosuid,nodev,noexec,relatime)
debugfs on /sys/kernel/debug type debugfs (rw,nosuid,nodev,noexec,relatime)
tracefs on /sys/kernel/tracing type tracefs (rw,nosuid,nodev,noexec,relatime)
fusectl on /sys/fs/fuse/connections type fusectl (rw,nosuid,nodev,noexec,relatime)
configfs on /sys/kernel/config type configfs (rw,nosuid,nodev,noexec,relatime)
ramfs on /run/credentials/systemd-sysusers.service type ramfs (ro,nosuid,nodev,noexec,relatime,mode=700)
/var/lib/snapd/snaps/core20_2264.snap on /snap/core20/2264 type squashfs (ro,nodev,relatime,errors=continue,threads=single,x-gdu.hide)
/var/lib/snapd/snaps/core20_2318.snap on /snap/core20/2318 type squashfs (ro,nodev,relatime,errors=continue,threads=single,x-gdu.hide)
/var/lib/snapd/snaps/lxd_28373.snap on /snap/lxd/28373 type squashfs (ro,nodev,relatime,errors=continue,threads=single,x-gdu.hide)
/var/lib/snapd/snaps/snapd_21465.snap on /snap/snapd/21465 type squashfs (ro,nodev,relatime,errors=continue,threads=single,x-gdu.hide)
/var/lib/snapd/snaps/snapd_21759.snap on /snap/snapd/21759 type squashfs (ro,nodev,relatime,errors=continue,threads=single,x-gdu.hide)
/dev/sda15 on /boot/efi type vfat (rw,relatime,fmask=0077,dmask=0077,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro)
binfmt_misc on /proc/sys/fs/binfmt_misc type binfmt_misc (rw,nosuid,nodev,noexec,relatime)
/dev/sdb1 on /mnt type ext4 (rw,relatime,x-systemd.requires=cloud-init.service,_netdev)
tmpfs on /run/user/1000 type tmpfs (rw,nosuid,nodev,relatime,size=85352k,nr_inodes=21338,mode=700,uid=1000,gid=1000,inode64)
tmpfs on /run/snapd/ns type tmpfs (rw,nosuid,nodev,size=170712k,nr_inodes=819200,mode=755,inode64)
nsfs on /run/snapd/ns/lxd.mnt type nsfs (rw)
overlay on /var/lib/docker/overlay2/fb105881cf0db3299b1114c878cbd14a70603c83d112a8e30facd3aa15a6cf3c/merged type overlay (rw,relatime,lowerdir=/var/lib/docker/overlay2/l/QF4G6GENHO5OKNLBDCACBBTI26:/var/lib/docker/overlay2/l/SDQGHINHI2TKTSYACVUMUAZKC6:/var/lib/docker/overlay2/l/MADWWRZQPTECR5AP2SMT6OJZUC:/var/lib/docker/overlay2/l/2VV3K3RRUSRMY6MFDGJ6REIUBF:/var/lib/docker/overlay2/l/T23LPZHDVB34ZFXVBHEMQDB7KO:/var/lib/docker/overlay2/l/KJFD6VK4P33OESIQEEFZLT5D24:/var/lib/docker/overlay2/l/POXKUZVVQ2PZHAJEAPI2UGFRDY:/var/lib/docker/overlay2/l/TGHDZFO6SL4ZVLSNY6XW3K7XCM:/var/lib/docker/overlay2/l/2MJZSBIFOBKFWUZTXDPPGRCRYX,upperdir=/var/lib/docker/overlay2/fb105881cf0db3299b1114c878cbd14a70603c83d112a8e30facd3aa15a6cf3c/diff,workdir=/var/lib/docker/overlay2/fb105881cf0db3299b1114c878cbd14a70603c83d112a8e30facd3aa15a6cf3c/work,nouserxattr)
nsfs on /run/docker/netns/12c7e4cd227c type nsfs (rw)

 

df -h 

ec2-user@Azure-Backend1:~$ df -h
Filesystem      Size  Used Avail Use% Mounted on
/dev/root        29G  4.0G   25G  14% /
tmpfs           417M     0  417M   0% /dev/shm
tmpfs           167M  1.1M  166M   1% /run
tmpfs           5.0M     0  5.0M   0% /run/lock
efivarfs        128K   37K   87K  30% /sys/firmware/efi/efivars
/dev/sda15      105M  6.1M   99M   6% /boot/efi
/dev/sdb1       3.9G   28K  3.7G   1% /mnt
tmpfs            84M  4.0K   84M   1% /run/user/1000

 

du -sh /*
du -sh

ec2-user@Azure-Backend1:~$ du -sh ./*
38M	./certbot
1.5M	./docker
0	./nest_server
190M	./source_nest_server
12K	./ssl

ec2-user@Azure-Backend1:~$ du -sh
368M

 

rc-local.service 상태조회

ec2-user@Azure-Backend1:~$ sudo service rc-local status
● rc-local.service - /etc/rc.d/rc.local Compatibility
     Loaded: loaded (/lib/systemd/system/rc-local.service; enabled; vendor preset: enabled)
    Drop-In: /usr/lib/systemd/system/rc-local.service.d
             └─debian.conf
     Active: active (exited) since Wed 2024-06-05 19:33:44 UTC; 13h ago
       Docs: man:systemd-rc-local-generator(8)
        CPU: 14ms

Jun 05 19:33:41 Azure-Backend1 systemd[1]: Starting /etc/rc.d/rc.local Compatibility...
Jun 05 19:33:41 Azure-Backend1 su[519]: (to ec2-user) root on none
Jun 05 19:33:42 Azure-Backend1 su[519]: pam_unix(su-l:session): session opened for user ec2-user(uid=1000) by (uid=0)
Jun 05 19:33:44 Azure-Backend1 rc.local[635]: bun run ./src/main.ts
Jun 05 19:33:44 Azure-Backend1 rc.local[635]: server pid is 640
Jun 05 19:33:44 Azure-Backend1 su[519]: pam_unix(su-l:session): session closed for user ec2-user
Jun 05 19:33:44 Azure-Backend1 systemd[1]: Started /etc/rc.d/rc.local Compatibility.

 

 

top

posted at

2024. 6. 6. 17:30


POST : Backend study

구글 GCP에서 AWS EC2 >> MS Azure로 서버를 옮겼다.

서버를 EC2로 옮길 결심을 하면서 백엔드 프레임웍도 바꾸기로 했다. Nginx에서 시작하여  deno + oak 로 동작하던 것을 Nginx를 빼고   bun(node.js) + nestjs ( fastify + gzip + ssl + file logging) 로 변경하는데 3주 정도 걸렸다. 다행히 typescript 그대로 사용하고  mongodb 다루는 것도 라이브러리 인터페이스가 동일해서 비지니스 로직이 크게 변경되는 것은 없었다. 

클라우드 전환은 EC2 1개 만들고 고정IP하나 할당한 것이 전부다.  EC2에 도커와 bun 설치하고 도커로 mongo db 설치한 후 소스코드 복사후 서버를 돌려봤다. 보름이 지났는데 아직까진 큰 문제는 없다. 사실 EC2로 옮기는 것은 1년전에 GCP에서 오라클 클라우드로 이전한 경험이 있어서 어렵지는 않았다. 오히려 로드 밸런서와 Nginx을 빼버려서 서버 구조가 간단해졌다. 

AWS를 사용하나 GCP를 사용하나 어차피 vm만 사용하는 거라 크게 다른 것은 없는 것 같다.  vm의 성능도 1명이 테스트 하는 것이라 체감상으로는 차이가 없다. 다만 비용적으로는 AWS가 조금 더 싸거나 비슷할 것이라 생각했는데 비용이 생각보다 많이 나왔다.  비용분석을 해보니 공인 IP 비용이 vm 사용료와 비슷하게 과금됬다. AWS가 최근에 공인 IP 사용을 유료화했기 때문이다. 대응책으로 EC2에 IPv6을 적용하는 것은 어렵지 않아 보인다. 다만 IPv6 서버에 접속하려면 단말도 IPv6가 되어야하는데 이게 쉽지 않다.  휴대폰이 Wifi가 아닌 3G 이상 일때는 문제가 없는 것 같은데 Wifi인 경우 공유기에서 IPv6를 지원하는 경우가 별로 없어 보인다.  테스트 단계라서 확실하지는 않다.  그냥 애저나 OCP로 가볼까 싶기도하다. 어차피 나만 쓰는 건데.. 

EC2를 장기 계약하는 방법으로도 vm 사용료를 줄일 수 있을듯 하다.  어차피 최소 사양이라 계산해 보니 1년에 2만원 조금 더 아낄 수 있는 수준이다. 아니면 EC2를 하루에 몇 시간만 돌리는 방법도 있다.  하지만 이것은 혼자 사용할 때나 가능한 옵션이고 혼자여도 매우 불편하기는 하다. 공인IP비용과 EBS는 장기 계약의 대상은 아닌듯.. 결국에는 빨리 서비스를 런칭해서 서버비 정도는 벌어보는 게 오늘의 결론인가.. 년 15만원 정도.. 결국은 모든 혁신과 추진력과 실행력은 돈이었다는..ㅎㅎ 돈이 많이 안 들어갈 때는 느긋했는데 1인 독푸딩 단계에서 이미 비용이 많아지는 느낌이라 고민이다.

 

=== 6/6일 추가  === 

급하게 애저로 vm을 변경해봤다. 애저로 변경시 장점: 12개월 무료 플랜 사용 가능.., 장기 예약 결재하면 비용 절감이 다른 곳에 비해 크다고 한다.  우분투 22.04 로 vm 생성 CentOS로 하려니 뭐가 잘 안 된다. CentOS버전은 왜 이리 많은건지 선택장애.. 오히려 우분투로 해도 크게 어려운 점은 없었다.  도커 설치만 조금 다를 뿐 나머지는 대동소이하다. 애저는 기본 로그인 아이디를 설정할 수 있는데 그냥 AWS와 동일하게 ec2-user로 했다. 대신 호스트 이름에 Azure_Backend1 으로 해서 명령줄에서 구분은 되게 했다. 

도커 설치
bun 설치
도커로 mongodb 설치
도커로 SSL 인증서 설치
서버 코드 복사후 서버 구동
재부팅시 서버 자동 실행 설정 (기본 설정의 스크립트 위치는 /etc/rc.local  이다)
Cron으로 db 백업 설정

AWS의 VM 중지
AWS의 공인 IP 연결 끊고 반납
AWS의 EBS는 당분간 유지(나중에 혹시 몰라) 이것도 조금이기는 하지만 비용이 나가니 애저 서버가 문제가 없다면  VM과 함께 삭제할 예정이다. 

내가 느끼기에는 vm은 모든 클라우드가 비슷하다. AWS가 1등이기는 하지만 다른 클라우드에 비해 비싼 것 같다. 다만 규모가 커졌을때 스케일 업이나 기타 여러가지 다른 서비스 연동시 차이가 있겠지만 현재 단계에서는 그것까지 고려하지 않아도 된다. 그리고 애저 리전을 한국으로 했더니 응답이 2~3배 빨라졌다. 

애저 시도하기전에 OCI 계정을 살려보려 했으나 실패했다. OCI는 로그인 할때, 오라클 계정과 사용자 이름 그리고 무슨 테넌시 인가하는 것을 입력하게 되어 있는데 왜 이렇게 만들었을까? 다 비슷해서 헤깔리고 오래되서 기억나지 않는다.  찾기를 하면 그런 email이 없다고 하고 다시 가입하려고 하면 중복된 email이라고 한다. 점유율이 2%인 이유가 있는것 같다.

 

=== 6/25일 추가 ===

애저 vm서버가 응답없음 상태로 종종 빠지는 문제가 있는데 해결을 못하고 있다. 자주 겪었다. 처음에는 원인을 몰랐는데 느낌적으로 정기적인 vm 업데이트 과정중에 발생하는 것 같다.  문제가 생기면 서비스 뿐만 아니라 ssh 접속이 안 되기 때문에 강제로 vm을 종료후 다시 시작해야한다. 강제 종료도 15분 이상 걸릴때가 있다.  vm포탈에서 보면 가상 머신  agent도 응답없음 상태라고 나온다. 

과도하게 Disk Read와 CPU 사용률을 보이는데 왜 그런지 몰랐는데. 

 

우현히 Agent가 뭔가 업데이트를 하고 있는 순간을 찍었다. 

 

그 후에 이렇게 다시 vm 응답없음이 되었다. 30gb SSD인데 654gb 디스크를 읽는게 정상인가?

????

일단 업데이트 정책을 변경해본다.  이미지 기본값으로 변경했다. 

==== 7/1 추가 ====

주의깊게 여러 테스트를 해 본 결과 bun을 사용시 vm이 network 응답없음이 되는 경우가 있었다. 이때까지 총 4번 정도 발생을 했고 애저 콘솔에서 bun install이나 bun run 을 실행했을 뿐인데, 동작중인 앱서비스가 서비스 불가가 되었고 ssh도 접속이 안 되었고, 역시 애저 에이전트도 응답없음이 되었다. 이게 버그라면 굉장히 치명적인 버그인데... 맥니미에서는 발생하지 않았고 생각해보니 AWS에서도 비슷한 일로 vm을 강제 종료했던 경험이 있는데 이것이 원인이 아닌가 의심이 든다. 아직 해결책은 모르겠다. 구글링해도 bun 실행이 hang이 걸린다는 것은 많으나 network를 망까뜨린다는 건 나오지 않는다. 

일단 대응은 vm이 죽으면 안 되기 때문에 bun대신에 node js로 서버를 돌리기로 했다. bun에서 그냥 지원되던 top-level await가 node js에서는 안 되기 때문에 조금 수정할 필요가 있었다. 하지만 Fastify를 사용하고 있어서 난해한 에러 1개가 있었는데 어찌어찌 해결했다. @fastify/compress 와 fastify 패키지를 같이 설치를 하면 app.registrer(compression) 부분에서 문법 오류가 나온다.  @fastify/compress 대신에 fastify-compress를 설치할 수 있는데 이건 deprecated되었고 버전도 맞지 않아서 역시 실행되지 않는다. 

'FastifyCompress' 형식의 인수는 'FastifyPluginCallback<FastifyCompressOptions> | FastifyPluginAsync<FastifyCompressOptions> | Promise<...> | Promise<...>' 형식의 매개 변수에 할당될 수 없습니다.\n  'FastifyCompress' 형식은 'FastifyPluginCallback<FastifyCompressOptions>' 형식에 할당할 수 없습니다.\n    'instance' 및 'instance' 매개 변수의 형식이 호환되지 않습니다.\n

https://github.com/nestjs/nest/issues/11265#issuecomment-2119100260 이쪽 링크를 참조해서 해결했다. (node_modules/@nestjs/platform-fastify/package.json 의 fastify 버전과 달라서 생긴 문제였고 맞추니 해결되었다. ) fastify 는 FastifyRequest와 FastifyReply를 참조해야해서 필요했다.  속도도 빠르고 여러가지 자체적으로 지원하는게 많아서 왠만하면 bun을 계속 사용하고 싶기는 하지만 node js로 돌려보고 동일한 문제가 다시 생기는지 확인해볼 필요가 있다.  mongodb upload하는 스크립트는 mongo_upload.ts는 cron서비스로 하루에 1번 실행되는데 이건 ts-node로 실행되도록 수정했다. 다행히 오류 없이 한 번에 실행되었다. bun사용과 비교했을때 npm install이 가장 시간이 오래걸린다. 

 

==== 7/6일 추가 ====

NODE js로 런타임을 변경후에 5일 간 서버가 죽지 않았다.  오늘 아침에 서버가 응답이 없었는데 그건 1달이 지나서 VMStoppedToWarnSubscription 가 발생했다. 계속하려면 업그레이드하라는 뜻이다. 업그레이드하면 12개월 무료 플랜은 계속 사용할 수 있고 일부는 유료로 계속 사용해야 한다.  보니깐 VM은 무료로 일정기간 무료로 사용할 수 있는 듯 하나 스토리지와 네트웍은 무료가 아니라서 조금 돈이 들것 같다. 

 

==== 9/1 일 추가 ====

애저 vm의 업데이트 정책을 원래대로 되돌려서 서버가 다운되는지 다시 테스트해 보았는데  한 달 넘게 문제가 없었다.  그 동안 업데이트 중에 리부팅이 한 번 되었는데 자동 시작 스크립트에 의해서 서버는 재시작되었고 29일 이상 문제없이 동작했다. 따라서 vm 응답없음의 원인은 bun js 런타임이라고 추정한다.  

 

 

top

posted at

2024. 6. 4. 22:53


POST : Backend study

Nest.js 서버에서 환율 조회 API

https://www.koreaexim.go.kr/ir/HPHKIR020M01?apino=2&viewtype=C&searchselect=&searchword= 여기서 제공하는 API를 사용하기로 한다. 

응답은 이렇다. prettyJson으로 보기 좋게 바꾼것이다. 

[
  {
    "result": 1,
    "cur_unit": "AED",
    "ttb": "366.42",
    "tts": "373.83",
    "deal_bas_r": "370.13",
    "bkpr": "370",
    "yy_efee_r": "0",
    "ten_dd_efee_r": "0",
    "kftc_bkpr": "370",
    "kftc_deal_bas_r": "370.13",
    "cur_nm": "아랍에미리트 디르함"
  },
  {
    "result": 1,
    "cur_unit": "AUD",
    "ttb": "894.96",
    "tts": "913.04",
    "deal_bas_r": "904",
    "bkpr": "904",
    "yy_efee_r": "0",
    "ten_dd_efee_r": "0",
    "kftc_bkpr": "904",
    "kftc_deal_bas_r": "904",
    "cur_nm": "호주 달러"
  },
  {
    "result": 1,
    "cur_unit": "BHD",
    "ttb": "3,570.03",
    "tts": "3,642.16",
    "deal_bas_r": "3,606.1",
    "bkpr": "3,606",
    "yy_efee_r": "0",
    "ten_dd_efee_r": "0",
    "kftc_bkpr": "3,606",
    "kftc_deal_bas_r": "3,606.1",
    "cur_nm": "바레인 디나르"
  },
  {
    "result": 1,
    "cur_unit": "BND",
    "ttb": "997.99",
    "tts": "1,018.16",
    "deal_bas_r": "1,008.08",
    "bkpr": "1,008",
    "yy_efee_r": "0",
    "ten_dd_efee_r": "0",
    "kftc_bkpr": "1,008",
    "kftc_deal_bas_r": "1,008.08",
    "cur_nm": "브루나이 달러"
  },
  {
    "result": 1,
    "cur_unit": "CAD",
    "ttb": "986.26",
    "tts": "1,006.19",
    "deal_bas_r": "996.23",
    "bkpr": "996",
    "yy_efee_r": "0",
    "ten_dd_efee_r": "0",
    "kftc_bkpr": "996",
    "kftc_deal_bas_r": "996.23",
    "cur_nm": "캐나다 달러"
  },
  {
    "result": 1,
    "cur_unit": "CHF",
    "ttb": "1,475.2",
    "tts": "1,505.01",
    "deal_bas_r": "1,490.11",
    "bkpr": "1,490",
    "yy_efee_r": "0",
    "ten_dd_efee_r": "0",
    "kftc_bkpr": "1,490",
    "kftc_deal_bas_r": "1,490.11",
    "cur_nm": "스위스 프랑"
  },
  {
    "result": 1,
    "cur_unit": "CNH",
    "ttb": "185.33",
    "tts": "189.08",
    "deal_bas_r": "187.21",
    "bkpr": "187",
    "yy_efee_r": "0",
    "ten_dd_efee_r": "0",
    "kftc_bkpr": "187",
    "kftc_deal_bas_r": "187.21",
    "cur_nm": "위안화"
  },
  {
    "result": 1,
    "cur_unit": "DKK",
    "ttb": "195.83",
    "tts": "199.78",
    "deal_bas_r": "197.81",
    "bkpr": "197",
    "yy_efee_r": "0",
    "ten_dd_efee_r": "0",
    "kftc_bkpr": "197",
    "kftc_deal_bas_r": "197.81",
    "cur_nm": "덴마아크 크로네"
  },
  {
    "result": 1,
    "cur_unit": "EUR",
    "ttb": "1,461.05",
    "tts": "1,490.56",
    "deal_bas_r": "1,475.81",
    "bkpr": "1,475",
    "yy_efee_r": "0",
    "ten_dd_efee_r": "0",
    "kftc_bkpr": "1,475",
    "kftc_deal_bas_r": "1,475.81",
    "cur_nm": "유로"
  },
  {
    "result": 1,
    "cur_unit": "GBP",
    "ttb": "1,717.24",
    "tts": "1,751.93",
    "deal_bas_r": "1,734.59",
    "bkpr": "1,734",
    "yy_efee_r": "0",
    "ten_dd_efee_r": "0",
    "kftc_bkpr": "1,734",
    "kftc_deal_bas_r": "1,734.59",
    "cur_nm": "영국 파운드"
  },
  {
    "result": 1,
    "cur_unit": "HKD",
    "ttb": "172.3",
    "tts": "175.79",
    "deal_bas_r": "174.05",
    "bkpr": "174",
    "yy_efee_r": "0",
    "ten_dd_efee_r": "0",
    "kftc_bkpr": "174",
    "kftc_deal_bas_r": "174.05",
    "cur_nm": "홍콩 달러"
  },
  {
    "result": 1,
    "cur_unit": "IDR(100)",
    "ttb": "8.36",
    "tts": "8.53",
    "deal_bas_r": "8.45",
    "bkpr": "8",
    "yy_efee_r": "0",
    "ten_dd_efee_r": "0",
    "kftc_bkpr": "8",
    "kftc_deal_bas_r": "8.45",
    "cur_nm": "인도네시아 루피아"
  },
  {
    "result": 1,
    "cur_unit": "JPY(100)",
    "ttb": "856.06",
    "tts": "873.35",
    "deal_bas_r": "864.71",
    "bkpr": "864",
    "yy_efee_r": "0",
    "ten_dd_efee_r": "0",
    "kftc_bkpr": "864",
    "kftc_deal_bas_r": "864.71",
    "cur_nm": "일본 옌"
  },
  {
    "result": 1,
    "cur_unit": "KRW",
    "ttb": "0",
    "tts": "0",
    "deal_bas_r": "1",
    "bkpr": "1",
    "yy_efee_r": "0",
    "ten_dd_efee_r": "0",
    "kftc_bkpr": "1",
    "kftc_deal_bas_r": "1",
    "cur_nm": "한국 원"
  },
  {
    "result": 1,
    "cur_unit": "KWD",
    "ttb": "4,388.2",
    "tts": "4,476.85",
    "deal_bas_r": "4,432.53",
    "bkpr": "4,432",
    "yy_efee_r": "0",
    "ten_dd_efee_r": "0",
    "kftc_bkpr": "4,432",
    "kftc_deal_bas_r": "4,432.53",
    "cur_nm": "쿠웨이트 디나르"
  },
  {
    "result": 1,
    "cur_unit": "MYR",
    "ttb": "286.74",
    "tts": "292.53",
    "deal_bas_r": "289.64",
    "bkpr": "289",
    "yy_efee_r": "0",
    "ten_dd_efee_r": "0",
    "kftc_bkpr": "289",
    "kftc_deal_bas_r": "289.64",
    "cur_nm": "말레이지아 링기트"
  },
  {
    "result": 1,
    "cur_unit": "NOK",
    "ttb": "128.02",
    "tts": "130.61",
    "deal_bas_r": "129.32",
    "bkpr": "129",
    "yy_efee_r": "0",
    "ten_dd_efee_r": "0",
    "kftc_bkpr": "129",
    "kftc_deal_bas_r": "129.32",
    "cur_nm": "노르웨이 크로네"
  },
  {
    "result": 1,
    "cur_unit": "NZD",
    "ttb": "826.85",
    "tts": "843.56",
    "deal_bas_r": "835.21",
    "bkpr": "835",
    "yy_efee_r": "0",
    "ten_dd_efee_r": "0",
    "kftc_bkpr": "835",
    "kftc_deal_bas_r": "835.21",
    "cur_nm": "뉴질랜드 달러"
  },
  {
    "result": 1,
    "cur_unit": "SAR",
    "ttb": "358.86",
    "tts": "366.11",
    "deal_bas_r": "362.49",
    "bkpr": "362",
    "yy_efee_r": "0",
    "ten_dd_efee_r": "0",
    "kftc_bkpr": "362",
    "kftc_deal_bas_r": "362.49",
    "cur_nm": "사우디 리얄"
  },
  {
    "result": 1,
    "cur_unit": "SEK",
    "ttb": "127.22",
    "tts": "129.79",
    "deal_bas_r": "128.51",
    "bkpr": "128",
    "yy_efee_r": "0",
    "ten_dd_efee_r": "0",
    "kftc_bkpr": "128",
    "kftc_deal_bas_r": "128.51",
    "cur_nm": "스웨덴 크로나"
  },
  {
    "result": 1,
    "cur_unit": "SGD",
    "ttb": "997.99",
    "tts": "1,018.16",
    "deal_bas_r": "1,008.08",
    "bkpr": "1,008",
    "yy_efee_r": "0",
    "ten_dd_efee_r": "0",
    "kftc_bkpr": "1,008",
    "kftc_deal_bas_r": "1,008.08",
    "cur_nm": "싱가포르 달러"
  },
  {
    "result": 1,
    "cur_unit": "THB",
    "ttb": "36.73",
    "tts": "37.48",
    "deal_bas_r": "37.11",
    "bkpr": "37",
    "yy_efee_r": "0",
    "ten_dd_efee_r": "0",
    "kftc_bkpr": "37",
    "kftc_deal_bas_r": "37.11",
    "cur_nm": "태국 바트"
  },
  {
    "result": 1,
    "cur_unit": "USD",
    "ttb": "1,345.9",
    "tts": "1,373.09",
    "deal_bas_r": "1,359.5",
    "bkpr": "1,359",
    "yy_efee_r": "0",
    "ten_dd_efee_r": "0",
    "kftc_bkpr": "1,359",
    "kftc_deal_bas_r": "1,359.5",
    "cur_nm": "미국 달러"
  }
]

인증에러가 나면 이렇다. (result가 1 : 성공, 2 : DATA코드 오류, 3 : 인증코드 오류, 4 : 일일제한횟수 마감)

[{"result":3,"cur_unit":null,"ttb":null,"tts":null,"deal_bas_r":null,"bkpr":null,"yy_efee_r":null,"ten_dd_efee_r":null,"kftc_bkpr":null,"kftc_deal_bas_r":null,"cur_nm":null}]

하루 1000번의 API 호출 개수 제한이 있기 때문에 앱에서 조회했다가는 하루에 1번 호출한다고 해도 1000명의 유저만 사용할 수 있다. 따라서 서버에서 매 시간 호출해서 값을 저장해 두었다가 별도의 API로 응답하는 것이 올바는 접근방식이다.  (서버에서 하루 24번만 호출하므로 갯수 제한에 걸리지 않는다.)

https://docs.nestjs.com/techniques/task-scheduling 를 이용하면 되겠다. 구현은 간단하다. 접근 방식은  월요일 부터 금요일까지 1시간 간격으로 호출해서 정상적인 응답이 있으면 메모리 캐시와 파일 캐시를 업데이트하는 식이다. 서버가 시작하면 파일 캐시에서 불러온다.  Cron 설정은 https://crontab.cronhub.io/ 를 참고하면 편하다. 

import { Injectable, Logger } from '@nestjs/common';
import { Cron } from '@nestjs/schedule';
import axios from 'axios';
import { DISCORD_WEBHOOK_URL, IS_PRODUCTION } from './app.const';
import { winstonLogger } from './logger.utils';
var fs = require('fs');


@Injectable()
export class TaskService {
  
  // 월에서 금요일까지 매 시간 30분 마다
  @Cron('30  *  *  *  1-5', {
    name: 'exchangeRateApi',
    timeZone: 'Asia/Seoul',
  })
  exchangeRateApi() {    
    fetchFromAPI();
  }
}

// API에서 응답할 캐시된 환욜 정보
export let exchangeRateResult = {};

const cachePath = __dirname + '/../../exchangeRate.json';

function fetchFromLocal() {
    fs.readFile(cachePath, 'utf8', (err, data) => {
        if (err){
            console.log(err);
            fetchFromAPI();
        } else {
            const json = JSON.parse(data);        
            if (typeof json.USD == "string") {
                exchangeRateResult = json;
                //console.log(json);
                winstonLogger.log(`load from exchange ratio cache USD: ${json.USD}`);
            }
        }
    });
}

function fetchFromAPI() {
    const API_KEY = "___API__AUTH__KEY____";
    const apiUrl = `https://www.koreaexim.go.kr/site/program/financial/exchangeJSON?authkey=${API_KEY}&data=AP01`;

    fetch(apiUrl)
    .then((response) => response.json())//읽어온 데이터를 json으로 변환
    .then((json) => {
        const result = {};
        json.forEach(item => {            
            const code = item.result;
            if (code == 1) { // 1 : 성공, 2 : DATA코드 오류, 3 : 인증코드 오류, 4 : 일일제한횟수 마감
                const cur_unit = item.cur_unit; //"USD"
                const deal_bas_r = item.deal_bas_r;
                result [cur_unit] = deal_bas_r;
            }
            
            if (IS_PRODUCTION && code != 1) {
                // KEY 인증오류
                axios.post(DISCORD_WEBHOOK_URL, {
                    content: "Exchage API error code:" + code,
                  });
                return false; // break forEach
            }
        });        

        if (typeof result.USD == "string") {            
            //update cache
            exchangeRateResult = result;

            // write to cache file
            const content = JSON.stringify(result);
            fs.writeFileSync(cachePath, content, 'utf8');            
            winstonLogger.log(`write exchange ratio USD:${result.USD}`)
        }
        
    });
}


fetchFromLocal();

 

fetch API에 error 핸들러를 빠트렸다. 이러면 서버가 죽는다.  마지막에 이것을 추가하자..

    })
    .catch((error) => {
        winstonLogger.warn(`fetch exchange error:${error.message}`)
    });

 

=== 6/26일 추가  ===

connection 오류가 자주 발생한다.  fetch API 때문인가 싶어서, axios로 바꿔도 동일한 오류가 뜬다. 

The socket connection was closed unexpectedly. For more information, pass `verbose: true` in the second argument to fetch()

하도 이상해서 curl 로 콘솔에서 테스트 했더니..

curl: (60) SSL certificate problem: unable to get local issuer certificate

해결책은 구글링하니 나오는데 서버의 ssl 인증서가 공인된 기관에서 인증받지 않아서라는데.. 

https://velog.io/@hyojinnnnnan/%EB%A6%AC%EB%88%85%EC%8A%A4-curl-60-SSL-certificate-problem-unable-to-get-local-issuer-certificate

 

 

이렇게 해도 안 되네. 다른 에러 메시지가

cause: UNABLE_TO_VERIFY_LEAF_SIGNATURE: unable to verify the first certificate

구글링해보니 보안적으로 위험하기는 하나 코드 마지막 줄에 아래 1줄을 추가.. 

process.env['NODE_TLS_REJECT_UNAUTHORIZED']="0";

를 추가해서 동작은 하기는 했다. 

 

 

그런데 또다시 이런게 뜨는데 이건 API 서버의 문제 같다. 뭐지??? curl로 로그를 보니 302 리다이렉트인데, 같은 URL로 계속 리다이렉트한다. . 

TooManyRedirects: The response redirected too many times.

 

애초에 웹브라우저로 하면 아무 문제 없는 URL인데 js, curl 에서 호출하면 에러라니.. 골치아프구나. 느낌적으로는 쿠키 문제 같은데 애초에 API에 쿠키가 동작해야 된다는게 이상한데..

 

Bun js 런타임이라서 생기는 문제인가 싶어서. node 20으로 다시 테스트..

var apiUrl = `https://www.koreaexim.go.kr/site/program/financial/exchangeJSON?authkey=${API_KEY}&data=AP01`;

console.log(`fetch ${apiUrl}`);

fetch(apiUrl, { verbose: true })
    .then((response) => response.json())
    .then((json) => {
        console.log(json);
    })
    .catch((error) => {
        console.log(error);
    });

이렇게 해도 에러

dajkim76@Kims-Mac-mini ~ % node test.js
fetch https://www.koreaexim.go.kr/site/program/financial/exchangeJSON?authkey=
TypeError: fetch failed
    at node:internal/deps/undici/undici:12502:13
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5) {
  [cause]: Error: unable to verify the first certificate
      at TLSSocket.onConnectSecure (node:_tls_wrap:1674:34)
      at TLSSocket.emit (node:events:519:28)
      at TLSSocket._finishInit (node:_tls_wrap:1085:8)
      at ssl.onhandshakedone (node:_tls_wrap:871:12) {
    code: 'UNABLE_TO_VERIFY_LEAF_SIGNATURE'
  }
}

fetch하기전에  아래 한 줄 추가했더니

process.env['NODE_TLS_REJECT_UNAUTHORIZED']="0";

역시 너무 많은  리다이렉트 에러.

dajkim76@Kims-Mac-mini ~ % node test.js
fetch https://www.koreaexim.go.kr/site/program/financial/exchangeJSON?authkey=
(node:17022) Warning: Setting the NODE_TLS_REJECT_UNAUTHORIZED environment variable to '0' makes TLS connections and HTTPS requests insecure by disabling certificate verification.
(Use `node --trace-warnings ...` to show where the warning was created)
TypeError: fetch failed
    at node:internal/deps/undici/undici:12502:13
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5) {
  [cause]: Error: redirect count exceeded
      at makeNetworkError (node:internal/deps/undici/undici:4563:35)
      at httpRedirectFetch (node:internal/deps/undici/undici:10156:32)
      at httpFetch (node:internal/deps/undici/undici:10128:28)
      at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
      at async node:internal/deps/undici/undici:9868:20
      at async mainFetch (node:internal/deps/undici/undici:9858:20)
      at async httpFetch (node:internal/deps/undici/undici:10128:22)
      at async node:internal/deps/undici/undici:9868:20
      at async mainFetch (node:internal/deps/undici/undici:9858:20)
      at async httpFetch (node:internal/deps/undici/undici:10128:22)
}

 

스크랩 방식으로 고쳐본다. 

npm init
npm install axios cheerio 
vi index.js

const axios = require("axios"); 
const cheerio = require("cheerio"); 
 
const requestExchangeList = async () => { 
	try { 
		const response = await axios.get("https://m.stock.naver.com/marketindex/home/exchangeRate/exchange"); 
		const html = response.data; 
		const $ = cheerio.load(html); 
		const exchangeList = []; 
 
		//<strong class="MainListItem_name__2Nl6J">유로/달러</strong>
        //<span class="MainListItem_price__dP8R6">1.0711</span>
		$("strong.MainListItem_name__2Nl6J").each((index, element) => {
		 	const name = $(element).text();
		 	const nextElement = $(element).next();		 	
		 	if (nextElement.hasClass("MainListItem_price__dP8R6")) {
			 	const price = nextElement.text();
			 	const [country, code] = name.split(' ');
			 	if (code !== undefined && code.length == 3) {
			 		const exchange = {name, price, code};
			 		exchangeList.push(exchange);
			 	}
			 }
		}); 
 
		return exchangeList; 
	} catch (error) { 
		throw error; 
	} 
}; 
 

requestExchangeList().then(exchangeList => console.log(exchangeList));

/*
--output--
dajkim76@Kims-Mac-mini ~/test/exchange % node index.js
[
  { name: '미국 USD', price: '1,392.50', code: 'USD' },
  { name: '유럽 EUR', price: '1,488.16', code: 'EUR' },
  { name: '일본 JPY', price: '866.66', code: 'JPY' },
  { name: '중국 CNY', price: '190.72', code: 'CNY' }
]
*/
top

posted at

2024. 5. 30. 19:54


POST : Backend study

서버 소스 배포, 서버 재시작 스크립트

거창하게 만들지 않고 초간단하게 맥미니에서 AWS에 업로드하는 스크립트와  AWS에서 올려진 소스코드를 압축풀고 서버 재시작하는 스크립트를 작성했다. 

./upload_nestserver_toaws.sh

#!/bin/sh

today=$(date +"%Y%m%d_%H%M%S")
suffix=$1

# only filename
key=nest_server_${today}_${suffix}

filename=${key}.tar.gz
echo "# create $filename"
git archive --format=tar.gz -o $filename HEAD:nest_server


echo "# mv to ~/backend/azure"
mv ./$filename ~/backend/azure

cd ~/backend/azure

echo "# create upload_command.txt"
echo "cd source_nest_server\\nput $filename" > upload_command.txt

echo "# upload $filename to AZURE"
sftp -i azure_backend1_key.pem -b upload_command.txt ec2-user@azure.mdiwebma.com

echo "# rm $filename"
rm $filename

# 서버 사이드에서 소스 압축풀고 npm 설치후에, 기존 서버 종료후, 새 서버를 재시작
ssh -i azure_backend1_key.pem ec2-user@azure.mdiwebma.com "cd ~/source_nest_server && ./deploy_source.sh ${key}"

--6/25 업데이트:  ssh로 원격 명령 실행으로 업로드와 서버 재시작까지 스크립트 하나로 동작하도록 했다.--

하루에 여러번 배포하더라도 초단위로 폴더가 생성되므로 문제가 없다. 원하다면 뒤에 피처이름을 적어줄 수 있다.  ./upload_nestserver_toaws.sh feature1  로 실행한다. ssh로 명령실행후에 계속 접속을 유지하고 있는 건 어쩔 수 없을 것 같다. 

적절히 스크립트를 재조정한다면 여러 서버에 동시에 소스 배포도 가능하다. 

~/source_nest_server/deploy_source.sh

vm에서 새로운 소스 압축을 풀고, 새로운 소스에서 서버를 재 지삭하는 코드이다.

#!/bin/sh

dir=$1
filename=$1.tar.gz

if [ -e $filename ]
then
	rm -rf $dir
	mkdir $dir
	mv $filename $dir
	cd $dir

	tar xvf $filename
	rm $filename

	echo "bun install"
	~/.bun/bin/bun install

	cd ~/nest_server
	./stop_server.sh
	cd ~/
	rm ~/nest_server
	ln -s ~/source_nest_server/$dir ~/nest_server
	cd ~/nest_server
	./start_server.sh
fi

 

top

posted at

2024. 5. 24. 20:51


CONTENTS

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