JAVA의 자료형

서버와의 통신을 위해서 프로그램을 만들 일이 있어서 이것저것 기초작업을 시작해보다가 의문이 있어서 자료형에 대해 찾아보았다.

C++는 char, short, int, int64 등의 자료형과 여기에 대한 unsigned형들, C#에서는 byte, int16, in32, int64 등의 자료형들을 사용해왔다. 이클립스에서 jsp 파일을 작성하다보니 unsigned형에 대해 코드힌트가 나오는 것이 없었다. 그래서 인터넷을 검색해보니 몇가지 자료가 나왔다.

  • char가 2바이트의 크기를 가진다는 것.
  • 모든 자료형에 부호값을 가지기 때문에 unsigned라는 자료형이 따로 존재하지 않는다.

이 두가지에 가장 많은 차이가 있는 것 같다.

이 문서를 읽어보면 좋을듯하다. http://www.tipssoft.com/bulletin/board.php?bo_table=FAQ&page=11&wr_id=788

자바에 대해 많이 익숙해졌다고 생각했는데 이런 중요한 차이점이 있다는 것을 이제야 알게되었다.

JAVA/JSP에서 reCAPTCHA 시스템 설치

smallCaptchaSpaceWithRoughAlpha

최근에 게시판을 만들어놨는데 공개는 해놓지 않았다. 그냥 비공개로 올려놨는데 스팸봇들이 어찌나 설치대는지… 그래서 오늘은 captcha 시스템을 찾아보았다. 캡챠(이렇게 발음하는게 맞는지 모르겠다.)는 스팸봇들이 글을 함부로 업로드할 수 없도록 해주는 시스템이다.

Captcha의 경우에는 여러가지가 있는데 그중 요즘 가장 유명한 reCAPTCHA에 대해 찾아보기로 했다. 사실은 다른 캡챠 시스템은 찾아보기가 귀찮아서 그냥 들어본 기억이 이것밖에 없어서 그냥 이걸 쓰기로 결정했다. 쿨럭….

reCAPTCHA는 http://www.google.com/recaptcha 에서 확인할 수 있다. recaptcha를 사용하려면 인증키를 받아야하는데 이건 구글 계정으로 로그인해야 받을 수 있다.

각 사이트를 하나 추가할 때마다 그 사이트에 맞는 공개키와 개인키를 발급 받는다. 이 키는 나중에 프로그래밍에 쓰인다.

로그인한 다음 사이트를 추가하고 키를 발급 받았다면 이제 본격적으로 사이트에 reCAPTCHA 기능을 넣으면 된다. 여러가지 언어로 지원이 되는데 내 경우에는 JAVA/JSP로 사이트를 구성했으므로 그에 맞는 문서를 찾았다. JAVA/JSP에서 reCAPTCHA를 쓰는 경우에는 https://developers.google.com/recaptcha/docs/java?hl=ko 에서 도움말을 볼 수 있다.

설치하는 방법은 별로 어렵진 않다.

일단 http://code.google.com/p/recaptcha/downloads/list?q=label:java-Latest 에서 라이브러리 파일을 다운로드 받고 압축을 푼다음 jar 파일을 lib 폴더에 복사한다.

글 입력 페이지 부분에 1 항목에 Client Side 부분에 있는 코드를 입력하면 된다. public_key와 private_key 부분에는 아까 발급 받은 페이지의 공개키와 개인키를 복사해서 넣어주면 된다.

그리고 입력된 글을 처리하는 페이지나 서블릿에서는 Server Side 부분에 있는 코드를 입력한다. 역시 private_key 부분에는 아까 발급 받은 페이지의 개인키를 복사해서 넣는다.

여기까지가 끝이다. 정말 끝이다. 테스트를 몇번 해보니 잘 작동한다.

너무 쉽게 성공해서 허무했다. 어려울줄 알았는데… 이걸로 스팸봇이 얼마나 막아지려나 한번 기대해봐야겠다.

MSSQL JDBC 버전별 다운로드 방법

요즘은 C와 윈도우프로그래밍을 하며 MSSQL을 좀 보고 있는 중이다.

어찌어찌하다보니 MSSQL의 데이터를 어떻게 웹에서 표현할까 생각해보다가 JSP에서 한번 보여줄 방법이 없나 찾아보았다.

PHP는 MSSQL 연결 모듈이 따로 있다. 내 서버의 CentOS에서는 PHP 패키지 버전이 5.3 버전이 되면서 레포지토리에서 제거되었다. (리눅스에서 MSSQL을 쓸일이 없을거라 생각해서 그런거였는지는 모르겠지만.)

어찌됐던 JSP에서 MSSQL에 접근하려면 JDBC 드라이버가 필요하다. ODBC나 아니면 다른 데이터베이스 연결방법이 있겠지만 역시나 제일 일반적인 방법이 JDBC일테고 나 역시도 JDBC에 익숙하니까. 어찌됐던 MSSQL용 JDBC 드라이버를 찾기 위해 검색해보았다.

…찾기 어렵다. 젠장.

네이버에서 찾아본 결과 예전 버전 JDBC만 자꾸 검색되었다.

마이크로소프트에서 찾아봤더니 너무 찾기 짜증났다. SQL 서버 홈페이지에서도 찾기 힘들고… 마이크로소프트 웹사이트는 검색하기도 힘들고 내가 원하는 검색결과가 잘 나오지도 않는다. 마이크로소프트 웹사이트는 검색기능을 좀 어떻게 바꿔줬으면 하는 생각이 참 크다.

나중에라도 다시 필요할까봐, 그리고 누군가가 필요할까봐 여기에 적어둔다.

당연하겠지만 각 드라이버 버전별로 지원하는 MSSQL 서버 버전이 다르다. 해당하는 웹페이지에 가서 맞는 버전으로 받아야한다.

내 윈도우 서버의 경우에는 MSSQL 2012 버전을 사용 중이라 4.0 버전을 받아서 사용해야할 것 같다.

아직 사용해본 것은 아닌지라 사용방법이나 사용기는 나중에.

JSP를 응용하여 계정용량 파악해보기

내 서버에서는 무료로 웹호스팅 서비스를 운영 중인데 사용자지원페이지를 만들어보았다.

사용자지원페이지에는 현재 계정용량 파악하기라는 메뉴를 넣기로 했는데 정작 사용자 계정용량 파악하는 방법을 찾을 수 없었다는 것.

그래서 예전에 PHP로 만들었던 기억과 구글에서 찾은 몇가지 정보를 더하여 계정용량 검색기능을 만들어보았다.

일단 리눅스서버에서 쓰는 계정용량 검색 명령어는 ‘du -shm /home/계정ID’의 형태이다. 이 시스템명령어를 수행하여 결과값을 가져오면 된다는게 기본 원리.

<%@page import="java.io.InputStreamReader"%>
<%@page import="java.io.BufferedReader"%>
<%
String command = "du -shm /home/" + id;

//계정 총용량 구하기
String info_space = "";
Process p = null;
BufferedReader br = null;

try {
    p = Runtime.getRuntime().exec(command);
    br = new BufferedReader(new InputStreamReader(p.getInputStream()));
    String line = null;
    while ((line = br.readLine()) != null) {
        line = line.split("/")[0].trim();
        info_space = "약 " + line + " MByte";
    }
} catch (Exception e) {
    info_space = "계정 총용량을 확인할 수 없습니다.";
}
%>

자바에서는 시스템명령을 수행할 때 Runtime 클래스를 사용하면 된다. 이 클래스를 사용해서 돌아온 결과값을 while을 이용하여 읽어들이면(여기에서 BufferedReader와 InputStreamReader 클래스가 필요하다.) 명령을 수행한 결과를 알 수 있다.

내가 뽑고 싶은건 계정사용 공간이었는데 리눅스의 du -shm 명령어는 잡다한 글귀가 붙어있기 때문에 이것을 split와 trim 함수를 이용해서 필요한 부분만 잘라주었다. 이 부분은 아마 정규표현식이나 아니면 다른 어떠한 방법으로 더 간단히 할 수 있을 것 같지만 일단 내가 아는 명령어는 이정도이므로. du 명령어의 옵션으로 준 -h는 사람이 읽기 쉬운 형태로 보여준다. 예를 들면, 기가바이트 단위의 데이터는 G로 메가바이트는 M로 표시해준다. 일관성이 없어지기 때문에 웹페이지에서도 이것을 표시할 때 일관성이 없어지게 되므로 추가해서 -m 옵션을 주어서 무조건 Mega Byte 단위로 처리하게 만들었다.

이 방법을 이용하면 생각보다 쉽게 계정용량을 MByte 단위로 환산하여 출력할 수 있게 된다.

한가지 주의할 점은 생각보다 계정용량이 정확하게는 표시되지 않는다. 또한 자바의 Runtime 클래스는 어찌된 영문인지 접근권한에 상관 없이 실행될 수 있는 것 같다. 악의적인 사용자가 rm 등의 명령어를 사용할 시 크게 문제가 될 수 있을 수 있으니 실행할 명령어를 파라미터로 받아서 수행한다거나 하는 경우는 절대 없어야 할 것 같다.

JSP/서블릿으로 Gmail을 이용하여 메일 보내기

사용자가 적은 내용을 내 메일계정으로 보내도록 만들려고 여기저기 찾아본 끝에 결국 완료했다.

필요한 라이브러리는 Javabeans Activation Framework (JAF)와 JavaMail 이 두가지이다.

두 라이브러리를 다운 받아 적당한 폴더에 넣는다. 내 경우에는 이클립스에서 프로젝트폴더/WebContent/WEB-INF/lib 에 압축을 푼 JAR 파일들을 복사해넣었다.

두 라이브러리를 설치했다면 코드를 작성한다. 내가 작성한 코드는 JSP에서 form을 이용해 이름, 이메일주소, 제목, 내용을 입력받고 이 내용을 서블릿으로 보내고 서블릿에서 파라미터로 받은 내용을 메일로 보낸 다음, 처리 결과에 따라 적절한 결과 페이지로 보내게 되어있는 구조이다.

JSP의 form 입력양식 페이지는 어려울게 없다. 문제는 서블릿 부분이었다.

인터넷을 찾아보니 소스가 많이 있긴한데 대부분은 로컬 메일서버를 이용하는 방법들이었고 내 서버 역시 SendMail이 있긴하지만 굳이 그걸 쓸 필요도 없고 지메일쪽의 SMTP를 쓰는게 더 나을 것 같아서 방법을 찾아보았다. 지메일쪽은 SSL을 이용한 인증을 하고 있어서 이것저것 옵션을 붙여야할 것들이 많았다.

SendMail.java 서블릿의 코드는 다음과 같다.

package mailer;

import java.io.IOException;
import java.util.Properties;

import javax.mail.Authenticator;
import javax.mail.Message;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Servlet implementation class SendMail
 */
public class SendMail extends HttpServlet {
  private static final long serialVersionUID = 1L;

    /**
     * @see HttpServlet#HttpServlet()
     */
    public SendMail() {
        super();
        // TODO Auto-generated constructor stub
    }

  /**
   * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
   */
  protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    // TODO Auto-generated method stub
  }

  /**
   * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
   */
  protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    // TODO Auto-generated method stub

    request.setCharacterEncoding("UTF-8");

    response.setContentType("text/html;charset=UTF-8");
    response.setCharacterEncoding("UTF-8");

    String m_name =		request.getParameter("name");
    String m_email =	request.getParameter("email");
    String m_title =	request.getParameter("title");
    String m_text =		request.getParameter("text");

    try {
      String mail_from =	m_name + "<" + m_email + ">";
      String mail_to =	"불법스팸대응센터<118@kisa.or.kr>";
      String title =		"테스트 이메일입니다. :: " + m_title;
      String contents =	"보낸 사람 :: " + m_name + "&lt;" + m_email + "&gt;<br><br>" + m_title + "<br><br>" + m_text;

      mail_from = new String(mail_from.getBytes("UTF-8"), "UTF-8");
      mail_to = new String(mail_to.getBytes("UTF-8"), "UTF-8");

      Properties props = new Properties();
      props.put("mail.transport.protocol", "smtp");
      props.put("mail.smtp.host", "smtp.gmail.com");
      props.put("mail.smtp.port", "465");
      props.put("mail.smtp.starttls.enable", "true");
      props.put("mail.smtp.socketFactory.port", "465");
      props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
      props.put("mail.smtp.socketFactory.fallback", "false");
      props.put("mail.smtp.auth", "true");

      Authenticator auth = new SMTPAuthenticator();

      Session sess = Session.getDefaultInstance(props, auth);

      MimeMessage msg = new MimeMessage(sess);

      msg.setFrom(new InternetAddress(mail_from));
      msg.setRecipient(Message.RecipientType.TO, new InternetAddress(mail_to));
      msg.setSubject(title, "UTF-8");
      msg.setContent(contents, "text/html; charset=UTF-8");
      msg.setHeader("Content-type", "text/html; charset=UTF-8");

      Transport.send(msg);

      response.sendRedirect("request_complete.jsp");
    } catch (Exception e) {
      response.sendRedirect("request_failed.jsp");
    } finally {

    }
  }

}

중요한건 인증에 대한 부분인데 Session 객체에 이 인증에 대한 객체를 넣어줘야한다. 이 인증 객체는 내가 참조한 다른 글이나 구글링을 해서 찾은 외국문서들에도 모두다 하나의 클래스로 구현하고 있었다. 굳이 왜 그래야하는지는 잘 모르겠지만 어쨌튼 나도 따로 클래스 하나로 구현했다.

SMTPAuthenticator.java의 코드이다.

/**
 *
 */
package mailer;

import javax.mail.PasswordAuthentication;
import javax.mail.Authenticator;

/**
 * @author viper9
 *
 */
public class SMTPAuthenticator extends Authenticator {
  public SMTPAuthenticator() {
    super();
  }

  public PasswordAuthentication getPasswordAuthentication() {
    String username = "지메일주소";
    String password = "지메일암호";
    return new PasswordAuthentication(username, password);
  }
}

Authenticator를 상속받는 이러한 인증객체를 따로 구현한 다음 위 서블릿 클래스와 같은 디렉토리에 넣는다.
이 서블릿 클래스에 파라미터로 name, email, title, text 파라미터를 주면 설정된 메일 주소로 메일을 보내게 된다. 받는 사람도 따로 받고 싶다면 역시 파라미터로 받으면 그만이다.

난 서버의 거의 모든 부분에서 UTF-8 인코딩만 쓰고 있고 MySQL도 UTF-8을 기준으로 구성되어있으며 자바 작업을 할 때도 UTF-8을 선호하는 편이라 입력되는 파라미터들을 모두 UTF-8로 처리해주며 작업했다.

작업하며 어려웠던건 인증객체를 생성할 때 사용하는 SMTPAuthenticator 클래스를 만들 때였는데 이게 이클립스의 자동완성을 믿고 하다가 스펠링이 몇개 틀려서 한참 애먹었다. 내가 참조하던 그 문서도 아마 이 스펠링이 틀렸던 것으로 기억한다.Authentication와 Authenticator 이 스펠링이 틀리면 자바가 계속 암호가 틀린거 아니냐고 에러를 출력한다. 굉장히 짜증나던 부분.

여튼 한참 디버깅 끝에 오류를 잡고 나서 작동해보니 메일은 잘 오는데 몇가지 문제점이 있었다.

  1. 메일을 보내는 속도가 빠르지 않다. 외국서버라 그런지 메일을 보내고 나서 2~3초 후에 결과가 수신되는 것 같다. form의 submit 버튼을 사용자가 연속으로 누를 가능성이 있어서 이 부분을 조치를 취해야할 것 같다.
  2. 메일이 바로바로 오지 않는다. 바로바로 올때도 있는데 안 그럴 때도 꽤 많다. 메일을 보내고 나서 몇초 후에 도착하는 일이 꽤 많았다.
  3. 이걸 이용해서 메일을 받아보면 보낸 사람이 SMTPAuthenticator 클래스에서 설정한 그 지메일주소로 설정된다. 내가 따로 지정해주고 싶은데 아무리 해도 되질 않았다. Properties에서 설정이 가능할 것 같기도한데 찾지 못했다. (혹시 아시는 분은 답글 좀 부탁드립니다.)

위 3번 문제 때문에 이걸 이용해서 메일을 보내면 보낸 사람 정보를 알수 없어서 아예 메일 본문에다가 보낸 사람의 이름과 메일주소까지 넣도록 서블릿 클래스에 추가해야했다.
메일러의 메일주소를 따로 부여하기 위해 noreply@83rpm.com으로 메일 주소를 하나 만들어줬다. (지메일은 만들고 한번 로그인해야 비로고 SMTP 서버를 사용가능하다.)

이로써 사용자요청사항을 받는 페이지는 적절히 구현되었다. 이걸 정리해서 아예 메일 송신 전용 클래스로 만들어놔야겠다.

2014년 5월 8일 추가.

이 소스를 이용해 테스트를 하는 사람이 너무 많아서 나에게 너무 많은 스팸메일이 왔다. 소스 안에 메일 주소를 불법스팸대응신고센터 이메일 주소로 변경해두었으니 이 소스를 사용하는 사람은 받는 사람의 메일 주소를 확인하고 수정해서 쓰면 되겠다.