WSL2가 나오며 기존의 WSL을 삭제하는 방법에 대해 알아봅니다.

 

 

 

오래된 WSL을 사용하는 우분투 제거하기.

 

먼저 윈도우 키를 눌러 "설정"을 검색해 설정 앱으로 이동합니다.

 

 

설정 앱에서 "앱" 항목을 클릭합니다.

 

 

앱 실행 별칭에 "Ubuntu"를 검색합니다. 구식 우분투를 선택한 뒤 "제거"를 진행해 주면 됩니다.

 

 

이후 터미널을 켜보면 WSL1을 사용하는 우분투가 더 이상 존재하지 않음을 확인할 수 있습니다.

 

 

 

 

Windows Terminal 설정 충돌 문제 수정하기.

 

WSL1을 사용하기 위해 Windows Terminal을 사용하시던 분이라면 설정 충돌이 발생할 수 있습니다: github.com/microsoft/terminal/issues/5458

 

MEGATHREAD: Breaking settings changes in version 0.11! · Issue #5458 · microsoft/terminal

BREAKING SETTINGS CHANGES IN 0.11 BREAKING CHANGE We've deleted a lot of legacy settings handling (#5190) Please see our blog post and our most recent status update for more information. If you...

github.com

설정 충돌이 발생하시는 분은 앱 실행 별칭에 "Terminal"을 검색 후 "고급 옵션"을 클릭해 "초기화"를 진행해주시면 정상적으로 동작합니다.

 

 

 

 

 

반응형

 

System.Drawing.Bitmap를 사용할 때 발생하는 "The type initializer for 'Gdip' threw an exception." 오류를 해결하는 방법에 대해 알아봅니다.

 

 

 

1. 상황

 

프로젝트에서 System.Drawing.Bitmap을 사용하려고 할 때 Exception이 발생하며 "The type initializer for 'Gdip' threw an exception."와 같은 메시지가 출력됩니다.

 

프로젝트는 Docker Desktop을 사용해 Docker를 지원하는 프로젝트였습니다.

 

 

 

2. 원인

 

MS에서 Docker 지원 프로젝트를 만들면 자동으로 생성해주는 Dockerfile에 의해 생성되는 컨테이너는 libgdiplus를 지원하지 않아 발생하는 문제였습니다.

 

 

 

 

3. 해결

 

Dockerfile에 libgidplus를 지원할 수 있는 코드를 추가해 주면 됩니다. 코드는 다음 글에서 참고하였습니다: Can't use System.Drawing.Common in microsoft/dotnet:runtime

 

Can't use System.Drawing.Common in microsoft/dotnet:runtime · Issue #618 · dotnet/dotnet-docker

Steps to reproduce the issue pull microsoft/dotnet:runtime add .net core app that is using System.Drawing.Common package try and run app Expected behavior Should be able to create thumbnail images ...

github.com

 

# Your Dockerfile
#...
# install System.Drawing native dependencies
RUN apt-get update \
    && apt-get install -y --allow-unauthenticated \
        libc6-dev \
        libgdiplus \
        libx11-dev \
     && rm -rf /var/lib/apt/lists/*
#...

 

위와 같이 Dockefile을 수정 후 코드를 실행하면 정상적으로 동작합니다.

 

 

 

 

 

반응형

 

fo-dicom을 사용해 dicom파일에서 이미지를 추출하는 방법에 대해 알아봅니다. 또한 해당 작업을 수행하며 겪은 "A generic error occurred in GDI+." 에러를 해결하는 방법에 대해서도 알아봅니다.

 

 

 

Git에 나온 방법을 그대로 사용할 수 없어서 이것저것 알아보다 사용하게 된 코드입니다.

 

// Dicom images
ImageManager.SetImplementation(new WinFormsImageManager());
DicomImage dcmImg = new DicomImage(dcmFile.Dataset);
filePath = string.Format("{0}/{1}.bmp", folderPath, fileNameWithoutExt);
System.Drawing.Bitmap dcmBitmap = dcmImg.RenderImage().AsClonedBitmap();
dcmBitmap.Save(folderPath, System.Drawing.Imaging.ImageFormat.Bmp);

 

해당 코드를 수행하면 Save에서 다음과 같은 오류를 마주하게 됩니다: A generic error occurred in GDI+.

 

위 오류를 해결하기 위해 fo-dicom 관련 글을 뒤져봤으나 해결 방법을 찾을 수 없었는데 fo-dicom이 아닌 다른 글에서 해결 방법을 찾을 수 있었습니다: A Generic error occurred in GDI+ in Bitmap.Save method

 

A Generic error occurred in GDI+ in Bitmap.Save method

I am working on to upload and save a thumbnail copy of that image in a thumbnail folder. I am using following link: http://weblogs.asp.net/markmcdonnell/archive/2008/03/09/resize-image-before-upl...

stackoverflow.com

 

결론부터 말하면 fo-dicom과 관련된 문제가 아닌 Bitmap과 관련된 문제였습니다. MSDN과 연결된 링크는 확인할 수 없었으나 발췌된 글은 다음과 같습니다.

 

When either a Bitmap object or an Image object is constructed from a file, the file remains locked for the lifetime of the object. As a result, you cannot change an image and save it back to the same file where it originated.
> Bitmap 객체 또는 Image 객체가 파일에서 생성되면 생성된 파일은 객체의 수명 동안 잠긴 상태로 유지됩니다. 따라서 이미지를 변경하거나 원본과 동일한 파일에 다시 저장할 수 없습니다.

 

따라서 문제 되는 부분은 Bitmap을 Save 하는 부분이었습니다. 이를 해결하기 위해 다음과 같이 코드를 수정해 해결할 수 있었습니다.

 

// Dicom images
ImageManager.SetImplementation(new WinFormsImageManager());
DicomImage dcmImg = new DicomImage(dcmFile.Dataset);
filePath = string.Format("{0}/{1}.jpg", folderPath, fileNameWithoutExt);
System.Drawing.Bitmap dcmBitmap = dcmImg.RenderImage().AsClonedBitmap();
using (MemoryStream ms = new MemoryStream())
{
	using (FileStream fs = new FileStream(filePath, FileMode.Create, FileAccess.ReadWrite))
	{
		dcmBitmap.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);
		byte[] bytesBitmap = ms.ToArray();
		fs.Write(bytesBitmap, 0, bytesBitmap.Length);
	}
}

 

정상적으로 코드가 수행되는 것을 확인할 수 있습니다.

 

 

 

 

 

 

반응형

 

String.IsNullOrEmpty와 String.IsNullOrWhiteSpace의 차이점에 대해서 알아봅니다.

 

 

 

String.IsNullOrEmpty와 String.IsNullOrWhiteSpace

 

두 메서드는 한글로 해석하면 비어있다와 공백이다 정도의 아주 미묘한 차이로 해석됩니다. 이게 한글로 해석하면 미묘한 것이 공백의 사전적 정의에 "아무것도 없이 비어 있음."이 포함되어 있습니다. 결국 똑같이 해석되는 것처럼 보입니다. 

 

하지만 C#에서 프로그래밍적으로 두 메서드는 엄연히 다른 메서드입니다. MSDN에 따르면 String.IsNullOrWhiteSpace에 대한 설명에서 다음과 같이 나와있습니다.

 

 

 

IsNullOrWhiteSpace는 우수한 성능을 제공한다는 점을 제외하면 다음 코드와 유사한 기능을 하는 편리한 메서드입니다.

return String.IsNullOrEmpty(value) || value.Trim().Length == 0;

공백 문자는 유니 코드 표준에 의해 정의됩니다. IsNullOrWhiteSpace 메서드는 Char.IsWhiteSpace 메서드에 의해 true 값을 반환하는 모든 문자를 공백 문자로 해석합니다.

 

결국 String.IsNullOrWhiteSpace 메서드는 매개변수에 유니코드에 표준에 의한 "공백 문자"가 있더라도 true를 반환합니다. 다음 예시를 보면 그 차이를 한눈에 이해할 수 있습니다.

 

string.IsNullOrWhiteSpace("\t"); //true
string.IsNullOrEmpty("\t"); //false

string.IsNullOrWhiteSpace(" "); //true
string.IsNullOrEmpty(" "); //false

string.IsNullOrWhiteSpace("\n"); //true
string.IsNullOrEmpty("\n"); //false

 

 

 

 

 

 

반응형

 

앞서 포스팅 한 글 중에 다음과 같은 글이 있었습니다.

 

2020/10/22 - [Programming/Databse] - [ORACLE] ORA-12516 tns:리스너가 프로토콜 스택과 일치하는 처리기를 찾을 수 없습니다.

 

[ORACLE] ORA-12516 tns:리스너가 프로토콜 스택과 일치하는 처리기를 찾을 수 없습니다.

ORA-12516 tns:listener could not find available handler with matching protocol stack. ORA-12516  tns:리스너가 프로토콜 스택과 일치하는 처리기를 찾을 수 없습니다. 1. 현상 개발 환경에선 발생..

smoh.tistory.com

 

한동안 잠잠하더니 다시 문제가 생기기 시작해 확인하며 겪은 일을 포스팅합니다.

 

 

 

1. 다시 생긴 ORA-12516과 원인

 

똑같은 문제가 재차 발생했습니다. 과거의 기억을 끄집어 내 세션을 확인해보니 어느새 가득 찬 세션의 수를 볼 수 있었습니다.

 

문제 되는 세션을 확인하기 위해 다음 쿼리를 수행합니다.

 

SELECT S.machine,
       TO_CHAR (S.LOGON_TIME, 'YYYY/MM/DD HH24:MI:SS') LOGON_TIME,
       S.sid,
       S.serial#,
       P.PID ORACLE_PID,
       P.SPID OS_PID,
       S.STATUS,
       S.USERNAME ORACLE_USER,
       S.OSUSER OS_USER,
       S.TERMINAL,
       S.PROGRAM
FROM V$PROCESS P LEFT OUTER JOIN V$SESSION S ON P.ADDR = S.PADDR
WHERE P.BACKGROUND IS NULL AND P.PID > 1
ORDER BY S.machine, TO_CHAR (S.LOGON_TIME, 'YYYY/MM/DD HH24:MI:SS');

 

세션 리스트가 끝도 없이 쏟아져 나오는 상황입니다. 찬찬히 보니 뭔가 이상한 점이 있습니다. 대부분의 세션의 STATUS값은 이미 INACTIVE입니다.

 

 

 

2. Session Kill

 

INACTIVE 된 세션을 죽이고자 합니다. 다음 쿼리를 사용해서요.

 

--ALTER SYSTEM KILL SESSION 'sid,serial#' IMMEDIATE
ALTER SYSTEM KILL SESSION '123,4' IMMEDIATE 

 

근데 세션이 수백 개가 넘어갑니다. 이 방법으로는 특정 세션을 죽일 순 있겠지만 해결책은 되지 못합니다.

 

 

 

3. 세션 타임 아웃 설정.

 

오라클 측에서 제공해주는 DCD(Dead Connection Detection) 기능 중에 EXPIRE_TIME이 있습니다. 

오라클 설치 경로로 이동해 sqlnet.ora 설정 파일에 설정해 줄 수 있습니다. 다음 경로로 이동해 sqlnet.ora 파일을 열어주세요.

 

%ORACLE_HOME%\NETWORK\ADMIN\sqlnet.ora

 

SQLNET.EXPIRE_TIME 항목을 설정해 줍시다. 전 SQLNET.EXPIRE_TIME= 60와 같이 설정해 두었습니다. 60분의 만료 시간을 두었습니다.

 

이후 리스너를 재시작하면 만료 시간이 적용됩니다. 다만, 이미 연결되어 있는 세션에는 적용되지 않음에 유의해 주세요.

 

 

 

 

 

반응형

 

오라클 데이터 베이스에서 이미 생성된 테이블에 컬럼을 추가하는 방법에 대해 알아봅니다.

 

 

 

1. 테이블에 컬럼 추가

 

ALTER TABLE users ADD email VARCHAR(32) NOT NULL;

users 테이블에 email 컬럼을 VARCHAR(32)로 생성하며 NULL을 혀용하지 않는다.

 

 

2. 테이블에서 컬럼 제거

 

ALTER TABLE users DROP COLUMN email;

users 테이블에서 email 컬럼을 제거한다.

 

 

 

 

 

반응형

 

앞선 글

 

Entity Framework Core를 처음 사용하고자 했습니다. MS Docs를 읽으며 자습서를 따라 하는 도중 문득 의문이 생겼습니다. 내가 그동안 알고 있던 패턴은 Repository 패턴인데 이 익숙한 패턴을 그대로 사용할 수는 없을까? 하는 생각이 들었습니다.

 

MS Docs에서는 Repository 패턴과 관련된 부분을 찾을 수 없었습니다. 혹시 이런 고민을 하고 있는 사람이 저뿐 아니라 다른 분도 있는지 구글링을 해 본 결과 다음과 같은 글을 확인할 수 있었습니다.

 

 

 

EF에서 저장소 패턴을 사용하는 것이 좋지 않은 이유

 

왜 Entity Framework 에서 Repository 패턴을 사용하면 안 될까요: Why shouldn't I use the repository pattern with Entity Framework?

 

Why shouldn't I use the repository pattern with Entity Framework?

During a job interview, I was asked to explain why the repository pattern isn't a good pattern to work with ORMs like Entity Framework. Why is this the case?

softwareengineering.stackexchange.com

 

해당 글을 번역해보자면 다음과 같습니다.

 

면접에서 Repository 패턴(이하 저장소 패턴)이 Entity Frmawork(이하 EF)와 같은 ORM과 함께 사용하는데 좋은 패턴이 아닌 이유를 설명하라는 질문을 받았습니다. 왜 그런 걸까요?

 

EF에서 저장소 패턴을 사용하지 않는 가장 큰 이유는 바로 EF가 이미 저장소 패턴을 구현하고 있기 때문입니다. DbContext는 여러분의 작업 단위이고 DbSet은 저장소가 됩니다. 이 위해 저장소 패턴으로 다른 계층을 구현한다면 중복될 뿐 아니라 유지 관리가 더 어려워지게 됩니다.

 

사람들은 디자인 패턴의 목적을 알지 못한 채 패턴을 사용하곤 합니다 저장소 패턴의 목적은 낮은 수준의 데이터베이스 쿼리 로직을 추상화하는 것입니다. 코드에 실제 SQL문을 작성하던 예전에는 저장소 패턴이 코드 베이스 전체에 흩어져 있는 각각의 SQL을 개별 메서드에서 한 곳으로 지역화(Localize)하는 방법 중 하나였습니다. EF와 같은 ORM을 사용하면 이러한 코드 추상화를 대체할 수 있으므로 저장소 패턴이 필요하지 않습니다.

 

그렇다고 ORM위에 추상화를 만드는 것은 나쁜 생각은 아닙니다. 데이터가 EF나 또 다른 Web API에서 오는지 신경 쓰지 않고 애플리케이션에서 사용할 수 있는  API를 구성하는 서비스 패턴을 사용한다고 가정해 봅시다. 애플리케이션에 필요한 데이터를 반환하기 위해 서비스 클래스에 메서드를 추가하기면 하면 되므로 훨씬 간단해집니다. 

 

예를 들어 To-do앱을 만들고 있다면 이번 주에 만기 되고 아직 완료되지 않은 항목을 반환하는 서비스 호출이 있을 수도 있습니다. 앱이 알고 있는 것은 이 정보를 원한다면 해당 메서드를 호출한다는 것입니다. 매서드 내부에서 EF와 상호작용이 진행됩니다. 나중에 상호작용 대상이 변경되더라도 이 메서드 외의 나머지 코드는 문제없이 작동합니다.

 

잠재적으로는 저장소 패턴을 사용하는 것을 주장하는 것처럼 들릴 수 있지만 여기서 주요한 차이점은 서비스가 더 얇은 계층이며 리포지토리가 계속 쿼리 하는 것과 달리 서비스는 완전히 만들어진 데이터를 반환하도록 되어있다는 점입니다.

 

 

 

맺는 글

 

위 글에서 "디자인 패턴의 목적을 알지 못한 채 패턴을 사용하곤 한다"는 글이 크게 와 닿았습니다. 다양한 패턴을 사용하기 위해 알아보곤 했는데 이 패턴을 왜 쓰고 무엇을 위해 사용되는 패턴인지 한번 더 확인해야겠다는 생각이 드는 글이었습니다. 물론 EF를 저장소 패턴과 같이 안 쓰는 이유에 대해서도 명확히 이해하게 되었습니다

 

 

 

 

 

반응형

 

Certbot을 이용해 인증서를 갱신하는 방법에 대해 알아봅니다.

 

 

 

인증서 갱신 하기

 

벌써 인증서를 발급받은 지 세 달이 흘렀나 봅니다. Let's Encrypt로 발급받은 인증서의 만료기간이 거의 다 되었다는 메일이 왔습니다. 인증서를 갱신해 주도록 합니다.

 

인증서를 갱신하는 방법은 매우 간단합니다. 먼저 다음 명령어를 통해 인증서 갱신을 시도해 봅니다.

 

> sudo certbot renew --dry-run

 

--dry-run 옵션을 주고 실행하면 실제로 갱신을 진행하는 것이 아닌 갱신을 한번 시도해 에러가 발생할지 아닐지를 미리 알아볼 수 있습니다.

 

 

에러가 발생하면 위와 같이 에러가 발생한 원인을 알려줍니다. 원인을 보니 80번 포트 바인딩에 실패해 발생한 것 같습니다. 서버에 돌고 있는 NGINX를 잠시 종료하고 다시 시도해 보았습니다.

 

 

이제 정상적으로 성공했습니다. 테스트가 성공했으니 --dry-run 옵션을 빼고 진짜 갱신을 수행해 줍니다.

 

> sudo certbot renew

 

 

정상적으로 인증서가 갱신되었습니다.

 

 

 

갱신된 인증서 확인하기

 

이제 인증서 갱신을 끝마쳤으니 NGINX를 재실행하기 전 인증서들이 제대로 갱신되었나 확인해 보도록 하겠습니다. 다음 명령어를 수행해 인증서 정보를 확인합니다.

 

> sudo certbot certificates

 

 

모든 인증서가 정상적으로 갱신이 된 것을 확인할 수 있습니다.

 

 

 

 

 

반응형

 

Ubuntu 20.04에 설치된 pgAdmin4의 여러 설정을 변경하는 방법에 대해 알아봅니다.

 

다음 글을 참고해 Postgresql 및 pgAdmin4를 준비합니다.

제 서버에선 NGINX가 돌고 있어서 pgAdmin4 설치 후 Apache Web Server 실행 도중 에러가 나 NGINX를 잠시 끈 상태로 설치를 했습니다.

 

계속 이렇게 둘 순 없어서 NGINX와 pgAdmin4를 동시에 사용할 수 있는 방법에 대해 알아봤습니다. 가장 먼저 Apache Web Server의 기본 동작 포트를 변경하도록 합니다.

 

> sudo vi /etc/apache2/port.conf

 

 

적당한 값으로 변경합니다. 다음으로 virtual host의 포트 정보도 변경해야 합니다.

 

> sudo vi /etc/apache2/sites-enabled/000-default.conf

 

 

앞서 변경한 값으로 맞춰줍니다. 이제 Apache Web Server를 재시작합니다.

 

> sudo service apache2 restart

 

그 후 http://your.domain.com:8080/pgadmin4로 접속합니다. 

 

 

정상 동작이 확인됩니다. 이제 NGINX 설정을 변경합니다.

 

> sudo vi /etc/nginx/conf.d/my-server.conf

 

 

** conf 파일의 위치 및 이름은 NGINX 구성에 따라 달라질 수 있습니다.

 

NGINX를 재시작합니다.

 

> sudo service niginx restart

 

이제 외부에서 http://your.domain.com/pgadmin4를 이용해 pgadmin4에 접속할 수 있습니다.

 

 

 

 

 

반응형

 

Postgresql을 위해 pgAdmin을 사용합니다. 이 글에선 Ubuntu 20.04에 pgAdmin을 설치하는 방법에 대해 알아봅니다.

 

 

 

1. pgAdmin이란

 

pgAdmin은 PostgreSql을 관리할 수 있는 PostgreSQL 전용 GUI 툴입니다.

 

 

psql과 같은 cli 클라이언트 툴을 사용하는데 불편해 GUI를 지원하는 툴이 개발되었으며 웹 버전과 데스크톱 버전으로 나뉘어 있습니다.

 

 

 

2. pgAdmin 설치

 

Ubuntu 20.04에 pgAdmin4를 설치해 보도록 합니다. 먼저 리포지토리를 위한 퍼블릭 키를 설치합니다. 

 

> sudo curl https://www.pgadmin.org/static/packages_pgadmin_org.pub | sudo apt-key add 

 

 

다음으로 리포지토리 설정 파일을 생성합니다.

 

> sudo sh -c 'echo "deb https://ftp.postgresql.org/pub/pgadmin/pgadmin4/apt/$(lsb_release -cs) pgadmin4 main" > /etc/apt/sources.list.d/pgadmin4.list && apt update'

 

 

이제 pgAdmin4를 설치할 수 있습니다. 다음 명령어로 pgAdmin4를 설치합니다.

 

> sudo apt install pgadmin4

 

 

만약 데스크톱 버전 혹인 웹 버전 둘 중 하나만 사용하고 싶다면 다음 명령어 중 하나를 골라 설치하면 됩니다.

 

> sudo apt install pgadmin4-desktop
> sudo apt install pgadmin4-web 

 

만약 웹 버전이 포함된 패키지를 설치했다면 pgAdmin-web을 설치해야 합니다. 다음 명령어를 통해 설치를 진행합니다.

 

> sudo /usr/pgadmin4/bin/setup-web.sh

 

 

이메일 주소와 비밀번호를 입력해줍니다. 지금 입력한 이메일 주소와 비밀번호가 초기 로그인 계정입니다.

 

 

pgAdmin4는 기본적으로 아파치 웹 서버 위에서 구동됩니다. y를 눌러 진행하면 http://your.domain.com/pgadmin4에 에 pgAdmin4를 접속할 수 있습니다. 

 

 

 

 

 

 

반응형

+ Recent posts