JSP MVC Model 2에 집중한 교재로 스프링을 사용하지 않고 서버를 개발하는 경우에 적합



Posted by 세상을 살아가는 사람
,

강의용 교재로 적당할 것 같다. 고급 수준은 아니지만 어느 정도 수준은 되기 때문에 학원 강의용으로는 적당할 것 같다.



Posted by 세상을 살아가는 사람
,

JSP/서브릿부터 스프링 MVC, 그 이상을 다루고 있어 수준이 있는 교재 같다. 비전공자에게는 다소 어려울 것 같아 걱정이 된다.



Posted by 세상을 살아가는 사람
,

한 학원에서 교재로 사용하고 있는 것이다.



Posted by 세상을 살아가는 사람
,

나는 webiopi라는 라즈베리파이 상에서 web으로 GPIO를 제어하는 프로그램을 참조하여

자바 스프링으로 유사한 기능을 제어하려고 하고 있다.

webiopi는 REST API를 사용하여 GPIO를 제어하고 있어서 자바 스프링에서 REST API를 어떻게 처리하는지 파악하고 있다.

이 글에서는 webiopi에서 제공하는 REST API 기능에 대하여 자바 스프링으로 처리하기 위한 전 단계로 REST API 처리를 위한 Controller, Method 및 입력 파라미터 파싱에 대하여 알아본다.

1. Get GPIO function

  - HTTP GET /GPIO/(gpioNumber)/function

package com.talanton.rest.controller;


import org.springframework.http.HttpStatus;

import org.springframework.http.ResponseEntity;

import org.springframework.web.bind.annotation.PathVariable;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RequestMethod;

import org.springframework.web.bind.annotation.RestController;


@RestController

@RequestMapping("/GPIO")

public class GpioController {

@RequestMapping(value="/{gpioNumber}/function", method=RequestMethod.GET)

public ResponseEntity<String> getFunction(@PathVariable("gpioNumber") Integer gpioNumber) {

ResponseEntity<String> entity = null;

String result = gpioNumber + " : in";

entity = new ResponseEntity<String>(result, HttpStatus.OK);

return entity;

}

}


2. Set GPIO function

  - HTTP POST /GPIO/(gpioNumber)/function/("in" or "out" or "pwm")

@RequestMapping(value="/{gpioNumber}/function/{value}", method=RequestMethod.POST)

public ResponseEntity<String> postFunction(@PathVariable("gpioNumber") Integer gpioNumber, @PathVariable("value") String value) {

ResponseEntity<String> entity = null;

String result = gpioNumber + " : " + value;

entity = new ResponseEntity<String>(result, HttpStatus.OK);

return entity;

}


3. Get GPIO value

  - HTTP GET /GPIO/(gpioNumber)/value

@RequestMapping(value="/{gpioNumber}/value", method=RequestMethod.GET)

public ResponseEntity<String> getValue(@PathVariable("gpioNumber") Integer gpioNumber) {

ResponseEntity<String> entity = null;

String result = gpioNumber + " : 0";

entity = new ResponseEntity<String>(result, HttpStatus.OK);

return entity;

}


4. Set GPIO value

  - HTTP POST /GPIO/(gpioNumber)/value/(0 or 1)

@RequestMapping(value="/{gpioNumber}/value/{value}", method=RequestMethod.POST)

public ResponseEntity<String> postValue(@PathVariable("gpioNumber") Integer gpioNumber, @PathVariable("value") Integer value) {

ResponseEntity<String> entity = null;

String result = gpioNumber + " : " + value;

entity = new ResponseEntity<String>(result, HttpStatus.OK);

return entity;

}


5. Output a single pulse

  - HTTP POST /GPIO/(gpioNumber)/pulse/

@RequestMapping(value="/{gpioNumber}/pulse", method=RequestMethod.POST)

public ResponseEntity<String> postPulse(@PathVariable("gpioNumber") Integer gpioNumber) {

ResponseEntity<String> entity = null;

String result = gpioNumber + " : " + "pulse";

entity = new ResponseEntity<String>(result, HttpStatus.OK);

return entity;

}


6. Output bit sequence

  - HTTP POST /GPIO/(gpioNumber)/sequence/(delay),(sequence)

@RequestMapping(value="/{gpioNumber}/sequence/{delay},{sequence}", method=RequestMethod.POST)

public ResponseEntity<String> postSequence(@PathVariable("gpioNumber") Integer gpioNumber,

@PathVariable("delay") Integer delay, @PathVariable("sequence") String sequence) {

ResponseEntity<String> entity = null;

String result = gpioNumber + " : " + "sequence : " + delay + " : " + sequence;

entity = new ResponseEntity<String>(result, HttpStatus.OK);

return entity;

}


7. Output PWM with a duty cycle ratio

  - HTTP POST /GPIO/(gpioNumber)/pulseRatio/(ratio)

// 소수점 데이터를 입력받기 위한 방법 {variable:.+}

// 참고 : http://stackoverflow.com/questions/16332092/spring-mvc-pathvariable-with-dot-is-getting-truncated

@RequestMapping(value="/{gpioNumber}/pulseRatio/{ratio:.+}", method=RequestMethod.POST)

public ResponseEntity<String> postPulseRatio(@PathVariable("gpioNumber") Integer gpioNumber,

@PathVariable("ratio") Float ratio) {

ResponseEntity<String> entity = null;

String result = gpioNumber + " : pulseRatio : " + ratio;

entity = new ResponseEntity<String>(result, HttpStatus.OK);

return entity;

}


8. Output PWM with an angle for servos

  - HTTP POST /GPIO/(gpioNumber)/pulseAngle/(angle)

@RequestMapping(value="/{gpioNumber}/pulseAngle/{angle}", method=RequestMethod.POST)

public ResponseEntity<String> postPulseAngle(@PathVariable("gpioNumber") Integer gpioNumber,

@PathVariable("angle") Integer angle) {

ResponseEntity<String> entity = null;

String result = gpioNumber + " : pulseAngle : " + angle;

entity = new ResponseEntity<String>(result, HttpStatus.OK);

return entity;

}


9. Call a macro on the server

  - HTTP POST /macros/(macro)/(args)

왜냐하면 URL이 /macros로 시작하기 때문이다. 기존의 GpioController는 REST API URL이 /GPIO로 시작하였기 때문에 사용할 수 없고, 새로운 MacroController를 사용한다.

package com.talanton.rest.controller;


import org.springframework.http.HttpStatus;

import org.springframework.http.ResponseEntity;

import org.springframework.web.bind.annotation.PathVariable;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RequestMethod;

import org.springframework.web.bind.annotation.RestController;


@RestController

@RequestMapping("/macros")

public class MacroController {


@RequestMapping(value="/{macro}/{args}", method=RequestMethod.POST)

public ResponseEntity<String> macroFunction(@PathVariable("macro") String macro, @PathVariable("args") String args) {

ResponseEntity<String> entity = null;

String result = "macroFunction : " + macro + ", " + args;

entity = new ResponseEntity<String>(result, HttpStatus.OK);

return entity;

}

}


10. Get full GPIO state/configuration

  - HTTP GET /*

    /* 라는 URL을 처리하기 위하여 새로운 RestController StateController를 정의한다.

package com.talanton.rest.controller;


import org.springframework.http.HttpStatus;

import org.springframework.http.ResponseEntity;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RequestMethod;

import org.springframework.web.bind.annotation.RestController;


@RestController

@RequestMapping("")

public class StateController {


@RequestMapping(value="*", method=RequestMethod.GET)

public ResponseEntity<String> gpioState() {

ResponseEntity<String> entity = null;

String result = "getState";

entity = new ResponseEntity<String>(result, HttpStatus.OK);

return entity;

}

}


이제는 Webiopi에서 제공하는 REST API에 대한 처리 함수와 입력 파라미터 파싱을 완료하였다. 이후 실제 라즈베리파이에 적용하여 기능을 개발하는 것이 필요하다.

Posted by 세상을 살아가는 사람
,

나는 안드로이드를 2010년에 배우면서 Twitter Open API를 사용하는 것을 익히게 되었다. 트위터에서는 트윗을 보낼 때 Restful API를 사용하여 안드로이드 앱에서 서버로 트윗을 전송하고, 또 서버에 있는 트윗들을 앱으로 다운로드 한다. 이와 같이 트위터에서 자신들의 서버에 REST API를 제공하여 많은 어플리케이션을 개발할 수 있도록 하고 있다.

우리도 서버를 개발하면서 이와 같이 서버의 기능을 REST API를 통해 Open API로 제공하므로써 서비스의 이용을 촉진하고 수익의 증대를 가져올 수 있다. 따라서 REST API를 웹에서 제공하는 방법을 알아본다. 여기서는 jsp 방식의 스프링을 기반으로 기술한다.

REST는 'REpresentational State Transfer'의 약어로 하나의 URI는 하나의 고유한 리소스(Resource)를 대표하도록 설계한다는 개념이다.

REST API는 외부에서 특정 URI를 통해서 사용자가 원하는 정보를 제공하는 방식이다. 최근에 Open API에서 많이 사용되면서 REST 방식으로 제공되는 외부 연결 URI를 REST API라고 하고, REST 방식의 서비스 제공이 가능한 것을 'Restful'하다고 표현한다.

스프링은

- 버전 3부터 @ResponseBody annotation을 지원하면서 본격적으로 REST 방식의 처리를 지원

- 그 이전부터 스프링은 Content Negotiation 등을 이용해서 처리할 수 있다.

- 스프링 버전 4에 들어와서 @RestController가 본격적으로 소개됨


1. @RestController의 소개

  - 스프링 4에서 REST 방식의 데이터 처리를 위해서 Controller 차원의 annotation을 지원

  - 스프링 3까지는 컨트롤러에서 @Controller annotation을 사용해서 처리하고, 화면 처리를 담당하는 JSP가 아닌 데이터 자체를 서비스하려면 해당 메소드나 리턴 타입에 @ResponseBody annotation을 추가하는 형태로 작성


2. Sample project 연습

우선 @RestController를 사용하여 jsp view 형태가 아니라 문자열의 형태로 서버로부터 응답을 받는 방법을 기술한다. 반환되는 형태는 단순 문자열 또는 JSON, XML 형태를 가진다.

2.1 테스트용 컨트롤러 생성하기

  - 새로운 스프링 MVC 프로젝트를 생성

    + 이클립스에서 File>New>Spring Legacy Project 선택

    + Project name을 입력하고, Templates로 Spring MVC Project 선택하고 Next 버튼 클릭

    + 적절한 package name을 입력하고, Finish 버튼을 클릭

  - pom.xml 수정

    + Properties에서 java-version : 1.6 -> 1.8

    + Properties에서 org-springframework-version : 3.1.1.RELEASE -> 4.1.7.RELEASE

  - Project Facets 수정

    + 이클립스에서 프로젝트를 선택하고 우클릭하고 Properties 선택

    + Project Facets 선택하고 Java Version을 1.8로 변경하고 Runtimes에서 Apache Tomcat v8.5 선택하고 OK 버튼 클릭

  - @RestController로 SampleController 생성

    + java source에서 새로운 package 생성 : com.example.restful.controller

    + 기존 HomeController를 controller package 밑으로 이동하고 package 정보를 맞추어 준다.

    + 새로운 SampleController 생성

package com.example.restful.controller;


import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RestController;


@RestController                    // REST API 기능을 처리하기 위해 @RestController annotation을 사용

@RequestMapping("/sample")  // path를 /sample로 지정

public class SampleController {


}


2.2 단순 문자열의 경우

  - @RestController에서 문자열 데이터는 기본적으로 브라우저에는 'text/html' 타입으로 처리된다.

  - 단순 문자열 처리를 위한 REST API를 만든다. (/sample/hello)

@RestController // REST API 기능을 처리하기 위해 @RestController annotation을 사용

@RequestMapping("/sample") // path(URI)를 /sample로 지정

public class SampleController {


@RequestMapping("/hello")    // path(URI)를 /sample/hello로 지정

public String sayHello() {        // method는 class내에서 path와 유사하게, 유닉하게 작명

return "Hello World!";    // 문자열(text/html) "Hello World!" 형태를 응답

}

}


  - contextPath 변경

    + 보통 REST API는 URI "/sample/hello"와 같이 contextPath가 "/"로 정의된다. 따라서 이클립스에서 default로 생성되는 contextPath를 변경해 주어야 한다.

    + Servers 창에서 Tomcat v8.5 Server at localhost를 double click하면 "Tomcat v8.5 Server at localhost" 창이 편집창에 생긴다. 여기서 Modules로 변경한 후, Web Modules에서 생성한 프로젝트를 선택 후 "Edit" 버튼을 클릭한다.

    + Path를 "/"로 수정하고 저장한다.

  - 다시 프로젝트를 선택하여 Tomcat을 구동한다. 그러면 "localhost:8080/"으로 구동됨을 확인할 수 있다.

  - 브라우저에서 "localhost:8080/sample/hello"를 입력하면 아래 그림과 같이 "Hello World!"가 출력됨을 확인할 수 있다.


2.3 객체를 JSON으로 반환하는 경우

  - 반환하는 형태가 문자열의 형태가 아니라 JSON 형태로 반환하는 방법을 기술한다.

  - 예를 들기 위하여 간단한 객체를 생성 (SampleVO)

package com.example.restful.domain;


public class SampleVO {

  private Integer mno;

  private String firstName;

  private String lastName;

  public Integer getMno() {

return mno;

  }

  public void setMno(Integer mno) {

this.mno = mno;

  }

  public String getFirstName() {

return firstName;

  }

  public void setFirstName(String firstName) {

this.firstName = firstName;

  }

  public String getLastName() {

return lastName;

  }

  public void setLastName(String lastName) {

this.lastName = lastName;

  }

  @Override

  public String toString() {

return "SampleVO [mno=" + mno + ", firstName=" + firstName + ", lastName=" + lastName + "]";

  }

}

  - 객체를 반환하는 REST API를 생성

  @RequestMapping("/sendVO")    // JSON 형식으로 객체 SampleVO를 반환하는 "/sample/sendVO" 작성

  public SampleVO sendVO() {       // SampleVO를 반환하는 sendVO method 정의

SampleVO vo = new SampleVO();     // SampleVO를 생성

vo.setMno(123);                            // 데이터를 삽입

vo.setFirstName("길동");

vo.setLastName("홍");

return vo;                                    // SampleVO 객체를 반환

  }


  - JSON 형태로 객체를 반환하기 위해서는 jackson-databind 라이브러리를 pom.xml에 추가로 정의해야 함

  <dependency>

<groupId>com.fasterxml.jackson.core</groupId>

<artifactId>jackson-databind</artifactId>

<version>2.8.4</version>

  </dependency>


  - 브라우저에서 접속 결과

   위 그림과 같이 JSON 형태의 SampleVO가 반환됨을 알 수 있다. 또한 아래 그림을 보면, Response Headers의 Content-Type으로 application/json 형태임을 확인할 수 있다.


2.4 컬렉션 타입의 객체를 반환하는 경우

  - 결과 데이터로 List 형태가 제공되는 경우에 대한 예를 들어 본다.

  @RequestMapping("/sendList")     // List 형태의 데이터를 반환하는 예 (/sample/sendList)

  public List<SampleVO> sendList() {    // List<SampleVO> 형태의 리스트를 반환하는 method sendList

List<SampleVO> list = new ArrayList<SampleVO>(); // List 생성

for(int i = 0;i < 10;i++) {    // 예로 10개의 SampleVO를 담는다.

SampleVO vo = new SampleVO(); // SampleVO를 생성

vo.setMno(i); // 데이터를 담는다.

vo.setFirstName("길동" + i);

vo.setLastName("홍");

list.add(vo); // list에 객체를 추가한다.

}

return list; // List 객체를 반환

  }

  - 아래 그림과 같이 JSON 형태로 List<SampleVO> list가 반환된다.


  - 결과 데이터로 Map 형태가 제공되는 경우에 대한 예를 들어 본다.

  @RequestMapping("/sendMap")

  public Map<Integer, SampleVO> sendMap() { // Map 형태의 데이터 반환하는 예 (/sample/sendMap)

Map<Integer, SampleVO> map = new HashMap<Integer, SampleVO>(); // Map 생성

for(int i = 0;i < 10;i++) { // 예로 10개의 SampleVO를 map에 추가

SampleVO vo = new SampleVO(); // SampleVO 생성

vo.setMno(i); // 데이터를 담는다.

vo.setFirstName("길동" + i);

vo.setLastName("홍");

map.put(i, vo); // Map에 SampleVO를 추가

}

return map; // Map 데이터를 반환

  }

  - 아래 그림은 Map 형태의 데이터를 JSON 형식으로 반환하는 결과이다.


Posted by 세상을 살아가는 사람
,

홈 페이지를 만들다보면 게시판을 여러 개를 만들 필요가 있다. 또한 게시판 마다 게시글의 읽기, 쓰기, 댓글달기, 첨부파일 달기 등의 권한을 달리 설정해주어야 하는 경우가 발생한다.

여기서는 그런 경우에 고려하여야 할 사항과 구현방법에 대하여 기술한다.

1. 요구사항

- 여러 개의 게시판을 운영한다. : 게시판 생성, 게시판 정보 변경, 게시판 삭제, 게시판 목록 보기, 게시판 정보 보기 등의 기능이 필요

- 게시판에 대한 속성 설정 : 읽기, 쓰기, 댓글달기, 비밀글 여부, 첨부파일 허용여부, 첨부파일 허용용량, 게시글 수정, 게시글 삭제

- 게시판 종류 : 기본, 갤러리, 자료실, Q&A, 공지사항, 방명록

  + 기본 게시판 : 제목, 작성자, 비밀번호, 내용, 첨부 1개

  + 갤러리 : 이미지 첨부만 가능, 첨부는 thumbnail로 처리

  + 자료실 : 첨부 파일은 5개까지 가능

  + Q&A : 비밀글 표시 및 비밀번호 검사 후 내용 표시

  + 공지사항 : 글쓰기 버튼 비활성화, 관리자만 쓰기 가능

  + 방명록 : 내용과 첨부 사진 올리기만 가능, 비밀글(쓰기, 답변)

여기에 기술된 요구사항은 대략적인 것으로 개발을 위해서는 구체적인 요구사항이 세분화 되어져야 한다.

2. 데이터베이스 테이블

2.1 board table

- board_id : 게시판을 식별하는 ID (정수)

- board_name : 게시판 이름

- board_type : 게시판 종류 (기본 게시판, 갤러리, 자료실, Q&A, FAQ, 공지사항, 방명록)

- url : 게시판에 접속하기 위한 URL 주소

- secret : 비밀글 여부

- read_allow : 읽기 허용 여부 (사용자 유형에 따라 제어 : 로그인 안했을 때, 로그인 했을 때, 관리자)

  + 모두 허용

  + 로그인한 경우 허용

  + 가입자 유형이 고객(customer)인 경우 허용

  + 가입자 유형이 상인(business)인 경우 허용

  + 특정 그룹에 속한 경우 허용 (추후 고려)

  + 관리자만 허용

- write_allow : 쓰기 허용 여부

  + 모두 허용

  + 로그인한 경우 허용

  + 가입자 유형이 고객(customer)인 경우 허용

  + 가입자 유형이 상인(business)인 경우 허용

  + 특정 사용자만 허용 : 별도의 쓰기 허용자 정보 저장 필요 (추후 고려)

  + 특정 그룹에 속한 경우 허용 (추후 고려)

  + 관리자만 허용

- reply_allow : 댓글 허용 여부

  + 모두 허용

  + 로그인한 경우 허용

  + 가입자 유형이 고객(customer)인 경우 허용

  + 가입자 유형이 상인(business)인 경우 허용

  + 특정 그룹에 속한 경우 허용 (추후 고려)

  + 관리자만 허용

- modify_allow : 수정 허용 여부

  + 모두 허용

  + 로그인한 경우 허용

  + 가입자 유형이 고객(customer)인 경우 허용

  + 가입자 유형이 상인(business)인 경우 허용

  + 작성자만 허용

  + 특정 그룹에 속한 경우 허용 (추후 고려)

  + 관리자만 허용

- delete_allow : 삭제 허용 여부

  + 작성자만 허용

  + 관리자만 허용

- download : 첨부파일 다운로드 허용 여부

  + 모두 허용

  + 로그인한 경우 허용

  + 가입자 유형이 고객(customer)인 경우 허용

  + 가입자 유형이 상인(business)인 경우 허용

  + 특정 그룹에 속한 경우 허용 (추후 고려)

  + 관리자만 허용

- upload : 첨부파일 업로드 허용 여부

  + 모두 허용

  + 로그인한 경우 허용

  + 가입자 유형이 고객(customer)인 경우 허용

  + 가입자 유형이 상인(business)인 경우 허용

  + 특정 사용자만 허용 (추후 고려)

  + 특정 그룹에 속한 경우 허용 (추후 고려)

  + 관리자만 허용

- nAttach : 첨부 가능한 파일의 수 (0~최대 5개)

- aSize : 첨부 가능한 파일의 용량 (최대 100MB까지)

  + 0, 50KB, 200KB, 1MB, 10MB, 100MB

- display_format : 본문 표시 여부

  + 1 : 제목만 표시

  + 2 : 본문(내용) 일부 표시

  + 3 : 본문(내용) 전부 표시

- board_desc : 게시판 설명

2.2 article table

- article_id : 게시글 id

- boardId : 게시판 id (하나의 테이블에 모든 게시판의 게시글을 저장하므로 게시판을 구분해 주어야 함)

- writer_name : 게시글 작성자 이름(O) -> 불필요할 수 있음(원 저자)

- email : 게시글 작성자 id (이메일 형식)

- title : 제목(O)

- author : 시와 같은 창작물을 게시할 때 원 저자를 입력(O)

- password : 비밀번호 (로그인 하지 않은 사용자가 게시글을 작성시, 추후 게시글 삭제 때 필요)

- read_count : 게시글을 읽은 횟수

- comment_count : 게시글에 대한 댓글 수

- like_count : 좋아요 수

- ref : 게시글과 댓글에 대한 정렬에 사용

- step : 게시글과 댓글에 대한 정렬에 사용

- depth : 게시글과 댓글에 대한 정렬에 사용

- createdAt : 게시글 작성 시간

- modifiedAt : 게시글 수정 시간

- content : 게시글 본문 내용

- ip : 사용자가 접속한 IP address

- secret : 비밀글 여부 (default false)

3. 추가로 고려되어야 할 사항

- 게시판 생성 후, 메뉴와의 연계방법 입니다. 동적으로 게시판을 생성하였더라도 그 게시판을 사용하려면 홈 페이지의 메뉴 체계와 동적으로 연계가 되어야 하는데 그 부분은 상당히 난이도가 있는 부분 같습니다. 전체 구조와도 연계가 되기 때문 입니다. 그 부분은 나중에 시간이 있을 때 고려하기로 합니다.


Posted by 세상을 살아가는 사람
,