이 글에서는 스프링 부트를 사용하여 포트폴리오를 만드는 과정을 기술한다.

개발환경은 다음과 같다.

- IDE : STS4

- 자바 : JDK 11.0.7

- 데이터베이스 : MySQL 8.0.29

먼저 STS4를 이용하여 프로젝트를 생성한다.

STS4에서 메뉴 File->New->Spring Start Project를 선택한다.

아래 그림과 같이 설정하고 Next 버튼을 클릭한다.

다음과 같이 dependencies를 설정하고 Finish 버튼을 클릭한다.

Spring Boot DevTools, Lombok, Spring Data JPA, Spring Security, Thymeleaf, Spring Web을 설정해 주었다. 예를 들어 Available에서 security를 입력하면 Security->Spring Security 항목을 입력할 수 있다.

아래 그림과 같은 폴더가 Package Explorer에서 생성됨을 알 수 있다.

build.gradle 파일을 열어보면 다음과 같은 정보가 있음을 알 수 있다.

plugins {
	id 'java'
	id 'war'
	id 'org.springframework.boot' version '2.7.6'
	id 'io.spring.dependency-management' version '1.0.15.RELEASE'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'

configurations {
	compileOnly {
		extendsFrom annotationProcessor
	}
}

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
	implementation 'org.springframework.boot:spring-boot-starter-security'
	implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5'
	compileOnly 'org.projectlombok:lombok'
	developmentOnly 'org.springframework.boot:spring-boot-devtools'
	annotationProcessor 'org.projectlombok:lombok'
	providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	testImplementation 'org.springframework.security:spring-security-test'
}

tasks.named('test') {
	useJUnitPlatform()
}

추가로 MySQL connector-java 라이브러리를 build.gradle dependency 부분에 추가한다.

	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	testImplementation 'org.springframework.security:spring-security-test'
	
	implementation("mysql:mysql-connector-java:8.0.29")	// 추가된 부분
}

 

데이터베이스 연동을 위해 MySQL 데이터베이스에서 database myapp을 생성하고, 사용자 'bootuser'@'localhost'와 'bootuser'@'%'를 추가하고, 사용자에게 테입터베이스 접근 권한을 부여한다.

-- 데이터베이스 생성

create database myapp;

-- 사용자 생성
create user 'bootuser'@'localhost' identified by 'bootuser';	-- 서버에서 접속할 수 있는 사용자
create user 'bootuser'@'%' identified by 'bootuser';			-- 다른 컴퓨터에서 접속할 수 있는 사용자

-- 권한 부여
grant all privileges on myapp.* to 'bootuser'@'localhost';
grant all privileges on myapp.* to 'bootuser'@'%';

-- 디스크에 저장
commit;

프로젝트의 설정 파일 application.properties 파일은 다음과 같다. JDBC 연동을 위한 Database 설정과 Spring Data JPA를 위한 설정을 추가하고, security 로그를 위한 설정을 추가한다.

# Database
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/myapp
spring.datasource.username=bootuser
spring.datasource.password=bootuser

# Spring Data JPA
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.show-sql=true

spring.thymeleaf.cache=false

# security
logging.level.org.springframework.security.web= debug
logging.level.kr.talanton.sboot.security = debug

아직 security를 위한 설정은 추가되지 않은 상태이다.

마우스로 프로젝트를 선택하고 우클릭 후 Gradle->Refresh Gradle Project를 선택하여 필요한 라이브러리를 다운로드 받는다. Project and External Dependencies에서 build.gradle에 설정한 라이브러리가 존재하는지 확인해 본다.

아래와 같이 기본 테스트 코드를 추가한 후 테스트 코드를 실행한다.

package com.example.sboot;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class SbootApplicationTests {

	@Test
	void contextLoads() {
		System.out.println("context loaded");
	}
}

간단하게 context가 로딩이 되는지 확인할 수 있는 코드이다. 테스트 코드를 마우스로 선택한 후 우클릭하고 Run AS->JUnit Test를 선택한다.

위 그림과 같이 security를 위한 user의 password가 추력되고, 테스트가 수행되어 context loaded가 출력됨을 알 수 있다.

또한 JUnit 테스트 수행결과가 표시됨을 알 수 있다.

소스 코드는 다음과 같다.

sboot.zip
0.08MB

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

1. 자바 JDK 11.0.6 설치

2. Tomcat 9 download 및 압축 풀기 : 버전 9.0.62

3. Eclipse IDE for Enterprise JAVA and Web Developer 2022-03-R(4.23) 버전 다움로드 및 압축 풀기

  - 2022-06-R(4.24) 버전이 새로 나온 것 같다. 이것을 설치해도 된다.

  - 이클립스는 내부에 JAVA Open JDK SE 16을 포함하고 있음.

  - Server -> Runtime Environment으로 Tomcat 9 등록 : JRE로 jdk-11.0.6을 지정 (이클립스 내부에 있는 것을 삭제하고, 새로 등록)

4. Dynamic Web Module Project 생성

  - Tomcat 9.0 선택

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

STS4를 사용하여 spring starter project를 Spring Data JPA를 설정하여 생성하면, 데이터베이스 설정이 필요하다.

여기서는 Oracle 19c 버전과 연동을 시키고자 한다. 이를 위해서 다음과 같이 설정이 되어야 한다.

1. build.gradle에 JDBC 라이브러리 추가

  - dependencies에 다음을 추가한다.

    implementation group: 'com.oracle.ojdbc', name: 'ojdbc8', version: '19.3.0.0'

  Project and External Dependencies에 ojdbc8.jar 파일이 생성되는 것을 확인할 수 있다.

2. src/main/resources 폴더에 있는 application.properties 파일에 오라클 데이터베이스 연동을 위한 설정을 추가한다.

spring.datasource.driver-class-name=oracle.jdbc.driver.OracleDriver
spring.datasource.url=jdbc:oracle:thin:@localhost:1521:orcl
spring.datasource.username=study
spring.datasource.password=study

3. Ex2Application.java 파일을 수행하면 에러 없이 실행되는 것을 알 수 있다.

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

채규태 지음 스프링 퀵 스타트는 H2 데이터베이스를 사용하고 있다.

나는 오라클 데이터베이스를 사용하여 실습을 진행하고자 한다.

이를 위해 변경사항을 정리해 본다.

1. 데이터베이스 테이블 스페이스 및 사용자 생성

- 사전 전제조건 : Oracle 데이터베이스 19c가 설치되어 있다. SQL developer에서 dba로 접속하여 다음과 SQL 작업을 한다.

- 테이블 스페이스 생성

  + 오라클 데이터베이스가 D:\oraclexe에 설치되어 oradata\ORCL 밑에 테이블 스페이스를 위한 dbf 파일을 생성한다.

create tablespace quickts
datafile 'D:\oraclexe\oradata\ORCL\quickts.dbf' size 100M
autoextend on next 5M;

- 사용자 생성 및 권한 부여

  + username : quick, password : quick

  + quick에게 일반 개발자 권한을 부여한다.

alter session set "_ORACLE_SCRIPT"=true;
create user quick identified by quick
default tablespace quickts temporary tablespace temp;

grant connect, resource to quick;  -- 접속, 자원 사용에 대한 권한
alter user quick quota unlimited on quickts; -- 테이블스페이스 사용 권한

commit;

  + quick 계정을 사용하여 sql developer에 접속 (SID = orcl)

 

2. 데이터베이스 테이블 생성

여기서는 Day 01 Class 6 비지니스 컴포넌트 실습 1을 실습하기 위하여 board 테이블을 생성한다. sql developer를 사용하여 사용자 quick로 접속하여 다음 SQL을 수행한다. seq 컬럼은 12c 이후에 추가된 기능을 사용하여 MySQL과 같이 auto_increment 기능을 적용하였다.

create table board (
    seq INTEGER GENERATED AS IDENTITY,
    title VARCHAR(100) not null,
    writer varchar2(50) not null,
    content varchar2(4000) not null,
    regDate date default sysdate,
    cnt integer default 0
);

alter table board add constraint pk_board primary key (seq);

 

3. JDBC 라이브러리를 사용하여 오라클 데이터베이스를 사용하기 위하여 다음과 같이 Connection을 가져온다. 이때 필요한 driver name, URL, username, password를 설정한다.

- JDBC 드라이브 이름 : oracle.jdbc.driver.OracleDriver

- 데이터베이스 URL : jdbc:oracle:thin:@localhost:1521:orcl

- Username : quick

- Password : quick

package com.springbook.biz.common;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;

public class JDBCUtil {
	private static final String DRIVER_NAME = "oracle.jdbc.driver.OracleDriver";
	private static final String URL = "jdbc:oracle:thin:@localhost:1521:orcl";
	private static final String USERNAME = "quick";
	private static final String PASSWORD = "quick";

	public static Connection getConnection() {
		try {
			Class.forName(DRIVER_NAME);
			return DriverManager.getConnection(URL, USERNAME, PASSWORD);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	public static void close(PreparedStatement stmt, Connection conn) {
		if (stmt != null) {
			try {
				if (!stmt.isClosed())
					stmt.close();
			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				stmt = null;
			}
		}
		if (conn != null) {
			try {
				if (!conn.isClosed())
					conn.close();
			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				conn = null;
			}
		}
	}

	public static void close(ResultSet rs, PreparedStatement stmt, Connection conn) {
		if (rs != null) {
			try {
				if (!rs.isClosed())
					rs.close();
			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				rs = null;
			}
		}
		if (stmt != null) {
			try {
				if (!stmt.isClosed())
					stmt.close();
			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				stmt = null;
			}
		}
		if (conn != null) {
			try {
				if (!conn.isClosed())
					conn.close();
			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				conn = null;
			}
		}
	}
}

4. BoardDAO.java를 수정

- 게시글 추가를 위한 SQL 문을 수정 : insert into board(title, writer, content) values(?,?,?)

package com.springbook.biz.board.impl;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;
import org.springframework.stereotype.Repository;
import com.springbook.biz.board.BoardVO;
import com.springbook.biz.common.JDBCUtil;

// DAO(Data Access Object)
@Repository("boardDAO")
public class BoardDAO {
	// JDBC 관련 변수
	private Connection conn = null;
	private PreparedStatement stmt = null;
	private ResultSet rs = null;
	// SQL 명령어들
	private final String BOARD_INSERT = "insert into board(title, writer, content) values(?,?,?)";
	private final String BOARD_UPDATE = "update board set title=?, content=? where seq=?";
	private final String BOARD_DELETE = "delete board where seq=?";
	private final String BOARD_GET = "select * from board where seq=?";
	private final String BOARD_LIST = "select * from board order by seq desc";

	// CRUD 기능의 메소드 구현
	// 글 등록
	public void insertBoard(BoardVO vo) {
		System.out.println("===> JDBC로 insertBoard() 기능 처리");
		try {
			conn = JDBCUtil.getConnection();
			stmt = conn.prepareStatement(BOARD_INSERT);
			stmt.setString(1, vo.getTitle());
			stmt.setString(2, vo.getWriter());
			stmt.setString(3, vo.getContent());
			stmt.executeUpdate();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			JDBCUtil.close(stmt, conn);
		}
	}

	// 글 수정
	public void updateBoard(BoardVO vo) {
		System.out.println("===> JDBC로 updateBoard() 기능 처리");
		try {
			conn = JDBCUtil.getConnection();
			stmt = conn.prepareStatement(BOARD_UPDATE);
			stmt.setString(1, vo.getTitle());
			stmt.setString(2, vo.getContent());
			stmt.setInt(3, vo.getSeq());
			stmt.executeUpdate();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			JDBCUtil.close(stmt, conn);
		}
	}

	// 글 삭제
	public void deleteBoard(BoardVO vo) {
		System.out.println("===> JDBC로 deleteBoard() 기능 처리");
		try {
			conn = JDBCUtil.getConnection();
			stmt = conn.prepareStatement(BOARD_DELETE);
			stmt.setInt(1, vo.getSeq());
			stmt.executeUpdate();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			JDBCUtil.close(stmt, conn);
		}
	}

	// 글 상세 조회
	public BoardVO getBoard(BoardVO vo) {
		System.out.println("===> JDBC로 getBoard() 기능 처리");
		BoardVO board = null;
		try {
			conn = JDBCUtil.getConnection();
			stmt = conn.prepareStatement(BOARD_GET);
			stmt.setInt(1, vo.getSeq());
			rs = stmt.executeQuery();
			if (rs.next()) {
				board = new BoardVO();
				board.setSeq(rs.getInt("SEQ"));
				board.setTitle(rs.getString("TITLE"));
				board.setWriter(rs.getString("WRITER"));
				board.setContent(rs.getString("CONTENT"));
				board.setRegDate(rs.getDate("REGDATE"));
				board.setCnt(rs.getInt("CNT"));
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			JDBCUtil.close(rs, stmt, conn);
		}
		return board;
	}

	// 글 목록 조회
	public List<BoardVO> getBoardList(BoardVO vo) {
		System.out.println("===> JDBC로 getBoardList() 기능 처리");
		List<BoardVO> boardList = new ArrayList<BoardVO>();
		try {
			conn = JDBCUtil.getConnection();
			stmt = conn.prepareStatement(BOARD_LIST);
			rs = stmt.executeQuery();
			while (rs.next()) {
				BoardVO board = new BoardVO();
				board.setSeq(rs.getInt("SEQ"));
				board.setTitle(rs.getString("TITLE"));
				board.setWriter(rs.getString("WRITER"));
				board.setContent(rs.getString("CONTENT"));
				board.setRegDate(rs.getDate("REGDATE"));
				board.setCnt(rs.getInt("CNT"));
				boardList.add(board);
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			JDBCUtil.close(rs, stmt, conn);
		}
		return boardList;
	}
}

5. 테스트 코드의 위치

교재에 있는대로 하면 src/test/java/BoardServiceClient.java가 동작하지 않고, Class Not Found Exception이 발생한다.

따라서 파일을 src/main/java/com.springbook.biz.board 패키지 밑으로 이동하여 실행한다.

게시글이 추가되고, 검색이 됨을 알 수 있다.

sql developer를 사용하여 테이블의 데이터를 확인한다.

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

자바를 이용한 스프링 강의를 계속하면서 그동안 변경된 개발 환경과 현재 시점에서 개발환경을 어떻게 설정을 해야 하는지 정리해 본다.
1. JDK 1.8을 사용한 개발 환경
- 처음에는 JDK 1.8을 사용하여 개발을 진행
- 이클립스 : 2020-06-R 버전을 사용하여 JAVA EE을 개발하였고, Eclipse Marketplace를 통해 STS를 설치하여 스프링을 개발하였었다.
- 그랬다가 어느 시점에 이클립스에서 STS plugin을 지원하지 않았다. 그래서 이클립스에서 플러긴을 사용하지 않고, 이클립스 기능을 제공하는 STS를 따로 설치하여 스프링을 개발하였다.
2. 별도의 STS를 사용한 스프링 개발 환경
- JDK 1.8을 지원하기 위하여 STS-3.9.15,RELEASE-e4.16.0을 사용
- 이클립스 버전도 JDK 1.8을 지원하기 위해 e4.16(2020-06-R) 버전인 것이다.
- JSP를 개발하기 위한 JAVA EE IDE도 eclipse-jee-2020-06-R 버전을 사용하였다.
3. JDK 11을 사용한 스프링 개발 환경
- 전자정부표준프레임워크 v4.0에서 JDK 11을 지원하고, 상용으로 JDK 11이 사용되게 되는 것 같다. 따라서 JDK 8을 사용하지 않고, JDK 11을 사용하는 환경을 고려해 본다.
- 자바 버전 : JDK 11.0.15
- 이클립스 : 2021-06-R Java EE IDE
- 이클립스 마켓 플레이스에서 "Spring Tools 3 Add-On for Spring Tools 4 3.9.22.RELEASE" Plugin을 설치하여 스프링 개발환경을 설정한다.
- 이때 pom.xml에서 다음과 같은 에러가 발생하면 다음 plugin 설정을 추가한다.
Description Resource Path Location Type
Could not initialize class org.apache.maven.plugin.war.util.WebappStructureSerializer pom.xml /ex08 line 1 Maven Configuration Problem

            <plugin>
            	<groupId>org.apache.maven.plugins</groupId>
            	<artifactId>maven-war-plugin</artifactId>
            	<version>3.3.1</version>
            </plugin>

- 별도의 STS를 사용하여도 되겠지만, 이클립스를 사용한 JSP 개발환경과의 연속성을 위해 이렇게 설정하는 것도 좋은 방법이다.
4. Spring Tools 4 (aka Spring Tool Suite 4) 4.15.1.RELEASE 설치
이클립스에서 STS plugin으로 . "Spring Tools 3 Add-On for Spring Tools 4 3.9.22.RELEASE"을 설치한 후에, Spring Tools 4 (aka Spring Tool Suite 4) 4.15.1.RELEASE을 설치하면, STS4를 설치할 수 있다.

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

5개월간 자바 웹 개발자 교육을 마치고, 포트폴리오를 수행한 결과물을 발표한 것을 녹화한 것이다.

https://youtu.be/9T8ZnKoFPew

1. Pa-Bi 팔고 비우고 - 중고 경매 플랫폼

2. 골라줘 점심즈

4. Momentum_extension

3. All Food In The World

그동안 교육받는라 수고한 수강생분들에게 박수를 보냅니다.

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 세상을 살아가는 사람
,

나는 맨처음에 안드로이드 앱을 배울 때, 트위터에서 제공하는 REST Open API를 이용하여 트위터와 유사한 앱을 개발하는 것을 배웠다. 또한 현재 운영되고 있는 "한국인이 좋아하는 명시" 앱도 트위터에서 제공하는 REST API를 사용하고 있다. 벌써 6년 전의 일이지만 아직도 이 앱이 사용이 되고 있고, 오늘도 어떤 사용자가 이 REST Open API를 사용하여 앱 전체 사용자에게 자신이 창작한 시를 전송하였다.

이 글에서는 REST Open API에 대한 개념을 좀 더 보충하기 위하여 트위터에서 제공하는 REST Open API 목록을 기술하고자 한다.

POST


이 목록들을 살펴보면 유사한 기능들이 묶여 있는 것을 알 수 있으며 서로 연관관계가 있는 것을 알 수 있다.

REST API를 사용하여 서버의 기능을 Open 하려면 어떻게 URI 체계를 가져가야 하는지를 살펴볼 수 있다.

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. 정의

  정규 표현식 또는 정규식은 특정한 규칙을 가진 문자열의 집합을 표현하는데 사용하는 언어 형식

  많은 텍스트 편집기와 프로그래밍 언어에서 문자열의 검색과 치환을 위해 지원

  자바스크립트 : 기능 내장

  자바 : 표준 라이브러리로 제공

2. 기본 개념

  주로 패턴으로 부르는 정규 표현식은 특정 목적을 위해 필요한 문자열 집합을 지정하기 위해 쓰이는 식

- 불린 "또는" : 수직선은 여러 항목 중 선택을 하기 위해 구분

  예) gray|grey -> gray 또는 grey와 일치

- 그룹 묶기 : 괄호를 사용하면 연산자의 범위와 우선권을 제어

  예) gray|grey와 gr(a|e)y는 'gray'나 'grey' 집합을 둘 다 기술하는 동일 패턴

- 양의 지정

  ?            : 0또는 1차례까지의 발생을 의미

    예) colou?r는 'color'와 'colour'를 둘다 일치

  *            : 0번 이상의 발생을 의미

    예) ab*c는 'ac', 'abc', 'abbc', 'abbbc' 등을 일치

  +           : 1번 이상의 발생을 의미

    예) ab+c는 'abc', 'abbc', 'abbbc' 등을 일치시키지만, 'ac'는 일치시키지 않음

  {n}         : 정확히 n번만큼 일치시킨다.

  {min,}     : 'min'번 이상만큼 일치

  {min,max} : 적어도 'min'번 만큼 일치시키지만 'max'번을 초과하여 일치시키지는 않는다.

3. 문법

3.1 POSIX 기본 및 확장 표준 문법

- 특수문자(?) : 

  + 외부 : 12개 ('.', '[', ']', '^', '$', '(', ')', '\', '*', ',', '{', '}', '-') 13개(?)

  + 내부 : 4개의 문자 ("\", "^", "-", "]") 자바와 닷넷은 "["를 포함


3.2 POSIX 확장 문법


3.3 문자 클래스


4. 예

- email : /^[-A-Za-z0-9_]+[-A-Za-z0-9_.]*[@]{1}[-A-Za-z0-9_]+[-A-Za-z0-9_.]*[.]{1}[A-Za-z]{2,5}$/

  + 처음 문자로 '-' 또는 대문자, 소문자, 숫자, '_' 로 시작 : 1회 이상(+)

  + 그 다음 문자로 '-', 대문자, 소문자, 숫자, '_' 문자가 0번 이상 (*)

  + '@' 문자가 정확히 1번은 일치

  + 그 다음 문자로 '-', 대문자, 소문자, 숫자, '_'가 1회 이상 (+)

  + 그 다음 문자로 '-', 대문자, 소문자, 숫자, '_', '.'가 0번 이상 (*)

  + '.' 문자는 정확히 1번은 일치

  + 마지막으로 문자로 대문자, 소문자가 최소 2번 이상 5번 초과 안되게

- 비밀번호 : /^(?=.*[a-zA-Z])(?=.*[!@#$%^*+=-])(?=.*[0-9]).{7,16}$/

  + 알파벳, 특수문자, 숫자가 최소 7개 이상 최대 16번 미만 입력

  + 특수문자, 영문, 숫자를 한번은 입력하되 8자 이상 16자 미만...


5. JSP 프로젝트 예

5.1 join.jsp (회원가입)

<script type="text/javascript" charset="utf-8" src="../js/member/join.js"></script>

  <form action="joinPro.jsp" method="post">

    <input type="email" id="id" name="id" placeholder="아이디 입력">

    <input type="password" id="pw" name="pw" placeholder="암호 입력">

    <input type="submit" value="회원가입">

  </form>

5.2 join.js

window.onload = function() {

  var id = document.getElementById("id");

  id.onkeyup = chkId;    // 키보드로 id 값을 입력하면 chkId 함수가 불린다.

  var pw = document.getElementById("pw");

  pw.onkeyup = chkPw;    // 키보드로 password를 입력하면 chkPw 함수가 불린다.

}

var chkId = function() {

var idReg = /^[-A-Za-z0-9_]+[-A-Za-z0-9_.]*[@]{1}[-A-Za-z0-9_]+[-A-Za-z0-9_.]*[.]{1}[A-Za-z]{2,5}$/;

if( !idReg.test(id.value) ) {    // 사용자가 입력한 것이 원하는 값이 아니면 'false'로 표시

id.style.color = "#EE5656";

passId = false;

}

else {                            // 원하는 패턴을 만족하면 'true'로 표시

id.style.color = "blue";

passId = true;

}

}


var chkPd = function() {

var pwReg = /^(?=.*[a-zA-Z])(?=.*[!@#$%^*+=-])(?=.*[0-9]).{7,16}$/;

if( !pwReg.test(pwd.value) ) {    // 사용자가 입력한 것이 원하는 패턴이 아니면 'false'로 표시

pwd.style.color = "#EE5656";

passPw1 = false;

}

else {                                // 원하는 패턴을 만족하면 'true'로 표시

pwd.style.color = "blue";

passPw1 = true;

}

}


정확한 사용은 조금 더 공부가 필요한 것 같네요...

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