외부 인증기관에서 인증서를 전달받지 않고, 내부적으로 사용할 RSA 키 페어가 필요하다면 아래 설명된 절차를 통해서 간단하게 키 페어를 만들어 낼 수 있다.

 

RSA key pair 생성

openssl genrsa -des3 -out private.pem 2048

 

private.pem파일을 열어보면 -----BEGIN RSA PRIVATE KEY----- 로 표시되는 것을 확인 할 수 있다.

 

Private key에 포함된 정보 확인

RSA private key로부터 public key를 만들어 낼 수 있다. 어떻게 그게 가능한지 보기 위해서 아래 명령어를 사용해보자.

 

openssl rsa -text -in private.pem

 

해당 .pem 파일로 필요한 .der 파일을 명령어로 만들 수 있다.

 

openssl rsa -in private.pem -outform der -out private.der

 

 

1. CloudFront

CloudFront는 정적, 동적 컨텐츠를 빠르게 응답하기 위한 캐시 기능을 제공하는 CDN 서비스입니다.

캐싱을 지원하기 때문에 S3에 저장된 컨텐츠를 직접 접근하지 않아도 됩니다.

따라서 S3의 비용이 감소하며, 더 빠른 응답을 지원하므로 꼭 함께 적용해주는 것이 좋습니다.

 

 

2. 퍼블리싱

src/main/resources/templates/gallery.html

<body>
    <h1>파일 업로드</h1> <hr>

    <form th:action="@{/gallery}" method="post" enctype="multipart/form-data">
        제목 : <input type="text" name="title"> <br>
        파일 : <input type="file" name="file"> <br>
        <button>등록하기</button>
    </form>

    <hr>

    <div th:each="gallery : ${galleryList}">
        <div style="margin-bottom: 30px">
            <p th:inline="text">제목 : [[${gallery.title}]]</p>
            <img th:src="${gallery.imgFullPath}" style="width: 500px; height: 300px;">
        </div>

        <form th:action="@{/gallery}" method="post" enctype="multipart/form-data">
            <input type="hidden" name="id" th:value="${gallery.id}">
            <input type="hidden" name="title" th:value="${gallery.title}">
            <input type="hidden" name="filePath" th:value="${gallery.filePath}">
            파일 : <input type="file" name="file"> <br>
            <button>수정하기</button>
        </form>
        <hr>
    </div>

</body>
  • 기존에 존재하던 gallery.html 파일에 갤러리 정보를 노출하는 영역과 수정 영역을 추가했습니다.
  • hidden 값인 id, title, filePath는 이미지 수정시 사용하는 값이므로 같이 넘겨줍니다.

 

3. 이미지 조회

1] 이미지 조회 - Controller

src/main/java/com/victolee/s3exam/controller/GalleryController.java

@GetMapping("/gallery")
public String dispWrite(Model model) {
    List<GalleryDto> galleryDtoList = galleryService.getList();

    model.addAttribute("galleryList", galleryDtoList);

    return "/gallery";
}
  • 기존에 존재하던 dispWrite() 메서드를 수정하여, 갤러리 목록을 가져옵니다.

 

2] 이미지 조회 - S3Service

S3에 이미지를 조회하는 방법으로 AmazonS3Client.getObject() 메서드가 있지만, 이는 S3에 직접 요청하여 객체를 가져옵니다.

여기서는 S3에 직접 접근하는 것이 아닌, CloudFront을 통해 캐싱된 이미지를 가져올 것이므로 해당 메서드를 사용하지 않습니다.

 

src/main/java/com/victolee/s3exam/service/S3Service.java

public static final String CLOUD_FRONT_DOMAIN_NAME = "dq582wpwqowa9.cloudfront.net";

...

public String upload(MultipartFile file) throws IOException {
    String fileName = file.getOriginalFilename();

    s3Client.putObject(new PutObjectRequest(bucket, fileName, file.getInputStream(), null)
            .withCannedAcl(CannedAccessControlList.PublicRead));

    return fileName;
}
  • 기존에 존재하던 upload() 메서드를 수정합니다.
  • CLOUD_FRONT_DOMAIN_NAME

 

3] 이미지 조회 - GalleryService

src/main/java/com/victolee/s3exam/service/GalleryService.java

public List<GalleryDto> getList() {
    List<GalleryEntity> galleryEntityList = galleryRepository.findAll();
    List<GalleryDto> galleryDtoList = new ArrayList<>();

    for (GalleryEntity galleryEntity : galleryEntityList) {
        galleryDtoList.add(convertEntityToDto(galleryEntity));
    }

    return galleryDtoList;
}

private GalleryDto convertEntityToDto(GalleryEntity galleryEntity) {
    return GalleryDto.builder()
            .id(galleryEntity.getId())
            .title(galleryEntity.getTitle())
            .filePath(galleryEntity.getFilePath())
            .imgFullPath("https://" + s3Service.CLOUD_FRONT_DOMAIN_NAME + "/" + galleryEntity.getFilePath())
            .build();
}
  • convertEntityToDto() 메서드는 Controller <--> Service 통신 간에 dto 객체를 사용하기 위해, repository로 부터 얻은 entity 객체를 dto로 변환하는 메서드입니다. 
    • .imgFullPath("https://" + s3Service.CLOUD_FRONT_DOMAIN_NAME + "/" + galleryEntity.getFilePath())​
      • 앞에서도 언급했지만 파일을 업로드할 때, 파일명만 DB에 저장하므로 이는 S3 객체의 key 값이 됩니다.
      • s3Service에 정의된 상수(CLOUD_FRONT_DOMAIN_NAME)를 불러와서 "CloudFront URL + key"를 조합하여 이미지 full path를 dto에 정의합니다.

 

4] 이미지 조회 - GalleryDto

src/main/java/com/victolee/s3exam/dto/GalleryDto.java

private String imgFullPath;

...

@Builder
public GalleryDto(Long id, String title, String filePath, String imgFullPath) {
    this.id = id;
    this.title = title;
    this.filePath = filePath;
    this.imgFullPath = imgFullPath;
}
  • 멤버 변수 imgFullPath를 추가하고, 빌더에도 정의해줍니다.

 

6] 이미지 조회 - GalleryRepository

src/main/java/com/victolee/s3exam/domain/repository/GalleryRepository.java

public interface GalleryRepository extends JpaRepository<GalleryEntity, Long> {
    @Override
    List<GalleryEntity> findAll();
}

 

 

5] 이미지 조회 - 테스트

 

이미지를 업로드하고 개발자도구로 src 애트리뷰트를 확인해보면 CloudFront URL로부터 이미지를 가져온 것을 확인할 수 있습니다.

 

 

 

"개발자 도구 - Network" 탭에서 Response Headers 정보를 살펴보면 cloudfront에서 Cache가 Hit된 것을 확인할 수 있습니다.

 

 

4. 이미지 수정/삭제

 

이미지 수정은 업로드와 마찬가지로 putObject() 메서드를 사용하여 객체를 수정합니다.

따라서 아무런 코드를 수정하지 않고 S3 이미지를 수정할 수 있습니다.

 

그런데 파일명이 같은 이미지를 수정했을 경우, S3에는 정상적으로 수정된 이미지로 업로드가 되지만 CloudFront는 캐시되어 있는 상태이므로 페이지에 새로운 이미지로 반영되지 않습니다.

CloudFront 배포 설정에 따라 캐싱되는 시간에 차이가 있는데, 기본 값은 1일 입니다.

어쨋든 이미지를 수정했음에도 바로 반영이 안되므로 이는 서비스 운영 관점에서 이슈입니다.

 

따라서 이를 해결하려면 아래의 작업이 필요하며, 그 이유는 다음과 같습니다.

  • 고유한 파일명(객체의 key)으로 S3에 업로드
  • 업로드 시, 해당 키의 객체가 존재하면 삭제 후 업로드

 

 

1] 이미지 수정/삭제 - Controller

src/main/java/com/victolee/s3exam/controller/GalleryController.java

@PostMapping("/gallery")
public String execWrite(GalleryDto galleryDto, MultipartFile file) throws IOException {
    String imgPath = s3Service.upload(galleryDto.getFilePath(), file);
    ...
}
  • service.upload()를 호출할 때, 기존의 파일명을 파라미터로 전달합니다.
  • 이 값은 gallery.html에 hidden 값으로 정의되어 있습니다.

 

2] 이미지 조회 - S3Service

src/main/java/com/victolee/s3exam/service/S3Service.java

public String upload(String currentFilePath, MultipartFile file) throws IOException {
    // 고유한 key 값을 갖기위해 현재 시간을 postfix로 붙여줌
    SimpleDateFormat date = new SimpleDateFormat("yyyymmddHHmmss");
    String fileName = file.getOriginalFilename() + "-" + date.format(new Date());

    // key가 존재하면 기존 파일은 삭제
    if ("".equals(currentFilePath) == false && currentFilePath != null) {
        boolean isExistObject = s3Client.doesObjectExist(bucket, currentFilePath);

        if (isExistObject == true) {
            s3Client.deleteObject(bucket, currentFilePath);
        }
    }

    // 파일 업로드
    s3Client.putObject(new PutObjectRequest(bucket, fileName, file.getInputStream(), null)
            .withCannedAcl(CannedAccessControlList.PublicRead));

    return fileName;
}
  • fileName 변수
  • doesObjectExist()
  • putObject()

 

2] 테스트

 

1) 기존 파일

 

 

2) 수정 업로드

 

동일한 파일명이지만 다른 그림을 업로드 했을 때, postfix로 인해서 CloudFront의 Cache가 적용되지 않은 것을 확인할 수 있습니다.

DB에도 잘 key값이 잘 반영되었네요.

 

 

3) S3에서 delete 되었는지 확인

 

신규/수정 업로드를 각각 수행했지만, 최종적으로는 마지막에 업로드한 파일만 존재하고 있는 것을 확인할 수 있습니다.

이상으로 Cloud Front를 적용했을 때 S3에 파일 업로드/조회 하는 방법에 대해 알아보았습니다.

 

 

출처: https://victorydntmd.tistory.com/336

build를 해도 계속 예전의 에러를 출력해내었다. 알아본 결과 gradle에 남아있는 cache 문제였다.

  1. C:\\Users\\사용자\\.gradle\\cache 내부의 폴더 삭제
  2. ./gradlew build --refresh-dependencies 혹은 재빌드

Post method 포스트맨 파일 업로드 실제 예제

 

Another version

Older version

 

Postman 사용시 유의.

코드 레벨에서 Content-Type: {multipart/form-data} 를 기입하지 않아야 에러가 나지 않는다.

혹은,

postman header 영역에서의 content-type: multipart/form-data 를 해제 시켜준다.

 

 

Fails If In Header 

Works 

 

출처: stackoverflow.com/questions/16015548/tool-for-sending-multipart-form-data-request

sudo apt-get purge runit 
sudo apt-get purge git-all 
sudo apt-get purge git 
sudo apt-get autoremove 
sudo apt update 
sudo apt install git

 

 

// git-core is already the newest version 로 검색

이유: Ubuntu 16.04 LTS에 runit이 upstart에 의존하고 Ubuntu가 15.04에서 systemd를 사용하도록 변경 되었기 때문에 문제가 발생함. (모든 git 패키지를 설치하는 중 에러)

 

 

'Server Enterprise > Node.js' 카테고리의 다른 글

[Intellij] Node.js 설정  (0) 2019.04.06

Reference
npmjs.com
searching 'express' 

expressjs.com
API reference > 4.x API > Request



Setting
node.js home directory move on
> npm init --yes (package.json create)

> npm i express (node express install) 

web server console easy handling module

> npm i -g nodemon

validation module

> npm i joi

> npm i joi@14.3.1

 

'Server Enterprise > Node.js' 카테고리의 다른 글

[Node.js] git-core is already the newest version  (0) 2020.10.27
Exception in thread "main" java.lang.IllegalAccessError: tried to access method org.apache.poi.util.POILogger.log(ILjava/lang/Object;)V from class org.apache.poi.openxml4j.opc.PackageRelationshipCollection
    at org.apache.poi.openxml4j.opc.PackageRelationshipCollection.parseRelationshipsPart(PackageRelationshipCollection.java:313)
    at org.apache.poi.openxml4j.opc.PackageRelationshipCollection.<init>(PackageRelationshipCollection.java:163)
    at org.apache.poi.openxml4j.opc.PackageRelationshipCollection.<init>(PackageRelationshipCollection.java:131)
    at org.apache.poi.openxml4j.opc.PackagePart.loadRelationships(PackagePart.java:561)
    at org.apache.poi.openxml4j.opc.PackagePart.<init>(PackagePart.java:109)
    at org.apache.poi.openxml4j.opc.PackagePart.<init>(PackagePart.java:80)
    at org.apache.poi.openxml4j.opc.PackagePart.<init>(PackagePart.java:125)
    at org.apache.poi.openxml4j.opc.ZipPackagePart.<init>(ZipPackagePart.java:78)
    at org.apache.poi.openxml4j.opc.ZipPackage.getPartsImpl(ZipPackage.java:243)
    at org.apache.poi.openxml4j.opc.OPCPackage.getParts(OPCPackage.java:684)
    at org.apache.poi.openxml4j.opc.OPCPackage.open(OPCPackage.java:275)
    at org.apache.poi.util.PackageHelper.open(PackageHelper.java:37)
    at org.apache.poi.xssf.usermodel.XSSFWorkbook.<init>(XSSFWorkbook.java:266)


Excel Download 해당 오류 경우

Dependency poi version을 맞춰주면 에러가 해결된다.



<properties>

<poi.version>3.13</poi.version>

</properties>


<dependencies>

<dependency>

<groupId>org.apache.poi</groupId>

<artifactId>poi</artifactId>

<version>${poi.version}</version>

</dependency>

<dependency>

<groupId>org.apache.poi</groupId>

<artifactId>poi-scratchpad</artifactId>

<version>${poi.version}</version>

</dependency>

<dependency>

<groupId>org.apache.poi</groupId>

<artifactId>poi-ooxml</artifactId>

<version>${poi.version}</version>

</dependency>

<!-- etc as needed -->

...

</dependencies>

자바 Split 함수 사용시에 "|" 로 자르는 경우 

엉뚱하게 잘리는 현상이 있다.


"\\|" 로 자르면 제대로 잘려진다.

json 파싱하기


이런 json 포맷을 이해할 수 있는 자료형으로 파싱하기 위한 다양한 라이브러리들이 있습니다.


기본적으로 java의 JsonObject와 JsonArray로 json을 파싱할 수 있습니다.


{

    "name": "hello!",

    "data": {

        "name": "jspiner",

        "age": 8,

        "birth": 1996

    },

    "friends": [

        "john",

        "smith",

        "sam"

    ],

    "books": [

        {

            "name": "book1",

            "price": 10000

        },

        {

            "name": "book2",

            "price": 15000

        },

        {

            "name": "book3",

            "price": 7000

        }

    ]

}




JsonParser jsonParser = new JsonParser();


JsonObject jsonObject = (JsonObject) jsonParser.parse(json);

JsonObject dataObject = (JsonObject) jsonObject.get("data");


System.out.print("name : " + dataObject.get("name"));

System.out.print("age : " + dataObject.get("age"));

System.out.print("birth : " + dataObject.get("birth"));







직렬화(Serialization) 이용하기


직렬화란 객체를 직렬화하여 전송 가능한 형태로 만드는것을 의미한다. 객체들의 데이터를 특정한 포맷의 연속적인 데이터로 변형하여 데이터를 읽도록 하는 것이다.


java에서는 직렬화를 지원하는 Gson Jackson등의 라이브러리가 있다.


JsonObject로 파싱하던 위 코드를 Gson으로 파싱하기 위해선 우선 객체를 담을 클래스를 구현한다.




class DataJson {


    @SerializedName("name")

    public String name;


    @SerializedName("data")

    public Data data;


    @SerializedName("books")

    public List<Book> books;


    class Data{

        @SerializedName("name")

        public String name;


        @SerializedName("age")

        public int age;


        @SerializedName("birth")

        public int birth;

    }


    class Book{

        @SerializedName("name")

        public String name;


        @SerializedName("price")

        public int price;

    }


}

// @SerializedName은 파싱할때 사용될 key 이름이다.




DataJson dataJson= new Gson().fromJson(json, DataJson.class);


System.out.println("name : " + dataJson.name);


for(DataJson.Book book : dataJson.books){

    System.out.println("book name : " + book.name);

    System.out.println("book price : " + book.price);

}



출처:https://calyfactory.github.io/%EC%A0%9C%EC%9D%B4%EC%8A%A8%ED%8C%8C%EC%8B%B1/

@RequestMapping의 produces 속성을 이용해 Response의 Content-Type을 제어할 수 있다


한국관광공사가 제공하는 TourAPI를 사용하면서 지역코드(area_code)로 지역명(area_name)을 조회하는 간단한 요청에서 encoding 문제에 부딪혔다. 아래와 같이 ajax 요청으로 areaCode를 보내면 서버에서 areaName을 return하는 식이다. 예를 들면, areaCode로 1을 보내면 서버는 "서울"을 응답해야 한다. 그런데 "서울"이 "??"로 출력되는 인코딩 문제가 발생했다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
$(function() {
    $.ajax({
        url: '/travel/getAreaName',
        method: 'get',
        data: {areaCode:'${content.area}'},
        dataType:"text",
        error:function(textStatus) {
            console.log(textStatus);
        },
        success:function(areaName) {
            console.log('선택된 지역 이름 : ' + areaName);
        }
    });
});
cs


이때까지 @ResponseBody를 사용하여 문자열을 return할 때 영어만 사용했어서 인코딩 문제를 겪지 않았었다. @RequestMapping의 produces 속성을 아래와 같이 작성하면 해결할 수 있다.


1
2
3
4
5
6
7
8
9
10
11
12
/**
 * 지역코드(areaCode)로 지역명을 조회한다
 * @param areaCode
 * @param resp
 * @return
 */
@RequestMapping(value="/getAreaName"
                method=RequestMethod.GET, 
                produces="application/text;charset=utf-8")
public @ResponseBody String getAreaName(@RequestParam int areaCode) {
    return tourApiModule.getAreaName(areaCode);
}
cs


아래는 produces 속성을 작성하지 않았을 때 Response Header의 Content-Type이다.



아래는 produces 속성을 작성한 후 Content-Type이 변경되어 응답하는 것을 확인할 수 있다.




출처: http://bigfat.tistory.com/103

+ Recent posts