java.net.SocketException: Connection reset 발생시 처리 방법

Published on: 2013. 11. 14. 14:13 by louis.dev
commons-net을 이용하여  ftp로 업로드 하는 중
java.net.SocketException: Connection reset
에러가 발생하면서 업로드에 실패했다. 자세히 로그를 살펴보니 FTP에 로그인까지 잘되었지만 파일업로드만 진행되지 않는 문제였다. 그래서 찾아보니 위 문제는 windows machine에서 java 7을 사용할때 발생되는 버그로 아래와 같이 해결할수 있다.
  1. 시작 -> cmd을 실행한다.
  2. 콘솔창에 netsh advfirewall set global StatefulFTP disable 을 입력한다.
이렇게 하면 간단하게 해결할 수 있다.

JAVA - java.lang.UnsupportedClassVersionError: Bad version number in .class file 에러 발생

Published on: 2011. 3. 8. 19:55 by louis.dev


위와 같은 Exception으로 검색하면 영문이던 한글이던 솔루션이 많이 나옵니다.

말그대로 .class를 만들때 ( 컴파일 시 ) 컴파일 버전이 달라 에러가 발생하는 것입니다.
예를들어 로컬에서 개발할때 컴파일 버전은 1.6으로 컴파일 했는데, 만약 배포할 서버의 컴파일 버전은 1.5 라면 다음과 같은 에러가 발생합니다.

물론 위와 같은 내용도 인터넷에 깔려 있습니다. 그리고 그 포스팅들의 솔루션은 "컴파일 레벨을 변경하라" 입니다. 

컴파일 레벨은  eclipse의 Window=> Preferences 를 선택하면 나오는 창에서 다음과 같이 컴파일러 레벨을 변경할 수 있습니다.

위와 같은 컴파일 설정은 글로벌한 컴파일 설정이고 각 프로젝트 마다 컴파일 레벨 설정을 하려면
프로젝트 선택 => 오른쪽 마우스 => Properties => Java Complier 에서 변경 할 수 있습니다.

그래서 전 혹시 몰라 두가지 모두 1.6 에서 1.5로 변경하고 서버 배포를 했습니다.

하지만 계속 "Bad version number in .class file" 이라는 에러가 뜨더라구요.

분명히 1.5로 바꿨는데 버전이 안맞는다니..ㅜㅜ

그래서 혹시 몰라 자바 update 버전 까지 맞아야 하나 라는 생각에, oracle사이트에 들어가 자바 업그레이드 버전까지 맞춰 주었지만 같은 문제가 발생했습니다. (결국 삽질이었다는 것을 느꼈죠..;;)

이때부터 패닉상태에 빠져 3시간을 허우적 대다가 같은팀 개발자분에게 도움을 요청한후 해결할 수 있었습니다.

일단. 다음의 코드로 해당 클래스 파일이 올바르게 컴파일 되었는지를 확인합니다.

import java.io.*;

public class Test {
    public static void main(String[] args) throws IOException {
    	checkClassVersion("알고자 하는 .class파일의 경로");
    }                      

    private static void checkClassVersion(String filename)
        throws IOException
    {
        DataInputStream in = new DataInputStream
         (new FileInputStream(filename));

        int magic = in.readInt();
        if(magic != 0xcafebabe) {
          System.out.println(filename + " is not a valid class!");;
        }
        int minor = in.readUnsignedShort();
        int major = in.readUnsignedShort();
        System.out.println(filename + ": " + major + " . " + minor);
        in.close();
    }
}

 이렇게 하시면 현재 컴파일된 class파일의 major와 minor가 나오는데 이를 통해 컴파일 버전을 유추할 수 있습니다.

 MAJOR MINOR  COMPILE VERSION 
 45  3  1.0
 45  3  1.1
 46  0  1.2
 47  0  1.3
 48  0  1.4
 49  0  1.5
 50  0  1.6

저는 위와 같은 코드로 값을 확인해 보니 MAJOR가 50. 즉 컴파일 버전이 1.6으로 자꾸 컴파일이 되는것이었습니다.

이클립스 설정을 통해 분명이 컴파일 레벨을 세팅해 주었음에도 불구하고 어떤 이유에서 인지 1.6으로 컴파일이 되었고 , 그로인해 Bad version number 에러가 발생했던 것이었습니다.

저는 ANT를 통해 컴파일 & 배포를 해왔기 때문에 ANT 컴파일 스크립트에 다음과 같이 추가 해준후 해결할 수 있었습니다.

<target name="02_compile" depends="01_init">
        <javac target="1.5" srcdir="${source}" destdir="${build.classes}" encoding="UTF-8" debug="true" deprecation="true" optimize="true">
        </javac>
</target>


다음과 같이 명시적으로 javac 태그 안에 target으로 컴파일러 버전을 세팅해 준후 배포하니 문제가 해결되었습니다.

무언가 설정이 잘못된 것일 수도 있지만. 무조건 eclipse를 맹신하면 이렇게 삽을 퍼야 하는 상황이 닥칠수 있으니 항상 의심하는 습관을 들여야 겠습니다.ㅜㅜ

cos.jar 의 FileRenamePolicy 만들기.

Published on: 2011. 2. 15. 09:52 by louis.dev

MultipartRequest를 통해 업로드를 하면, 생성자에 FileRenamePolicy 인스턴스를 파라미터로 전달합니다.

일반적으로 DefaultFileRenamePolicy를 많이 사용하게 되는데요.

이 DefaultFileRenamePolicy는 간단한 역할을 합니다.

public class DefaultFileRenamePolicy implements FileRenamePolicy {
  
  // This method does not need to be synchronized because createNewFile()
  // is atomic and used here to mark when a file name is chosen
  public File rename(File f) {
    if (createNewFile(f)) {
      return f;
    }
    String name = f.getName();
    String body = null;
    String ext = null;

    int dot = name.lastIndexOf(".");
    if (dot != -1) {
      body = name.substring(0, dot);
      ext = name.substring(dot);  // includes "."
    }
    else {
      body = name;
      ext = "";
    }

    // Increase the count until an empty spot is found.
    // Max out at 9999 to avoid an infinite loop caused by a persistent
    // IOException, like when the destination dir becomes non-writable.
    // We don't pass the exception up because our job is just to rename,
    // and the caller will hit any IOException in normal processing.
    int count = 0;
    while (!createNewFile(f) && count < 9999) {
      count++;
      String newName = body + count + ext;
      f = new File(f.getParent(), newName);
    }

    return f;
  }

  private boolean createNewFile(File f) {
    try {
      return f.createNewFile();
    }
    catch (IOException ignored) {
      return false;
    }
  }
}


코드에서 보시다 시피 중복된 파일이 있으면 파일이름 뒤에 1~9999까지의 숫자를 붙여서 파일의 rename을 진행하고 있는것을 확인 할 수 있습니다.

만약 서버의 업로드 서버의 인코딩 설정 문제로 한글이 깨지거나 할수 있습니다. 그렇기 때문에 저는 Timestamp를 통해 파일이름을 변경하고 있습니다. 이렇듯 MultipartRequest에서 File Rename Policy를 Timestamp로 변경하는 방법을 진행해 보겠습니다.

package net.tutorial.util;

import java.io.File;

import com.oreilly.servlet.multipart.FileRenamePolicy;

public class TimestampFileRenamePolicy implements FileRenamePolicy {
	@Override
	public File rename(File f) {
		String name = f.getName();
		String body = null;
		String ext = null;

		int dot = name.lastIndexOf(".");
		if (dot != -1) {
			ext = name.substring(dot); // includes "."
		} else {
			ext = "";
		}
		body = Long.toString( System.currentTimeMillis() );

		File renameFile = new File( f.getParent(), body + ext );
		
		if( renameFile.exists() ){
			int count = 0;
			do {
				count++;
				String newName = body + count + ext;
				renameFile = new File(f.getParent(), newName);
			}while( renameFile.exists() && count < 9999 );
			
		}
		f.renameTo( renameFile );
		return renameFile;
		
	}
}


System.currentTimeMillis()를 통해 현재의 시간을 ms 단위로 구한뒤에 이를 파일이름에 붙입니다. 만약 그럴일은 없겠지만..^^ 동시에 파일 업로드를 진행해서 파일이름이 똑같다면, Timestamp뒤에 1~9999를 붙여서 처리하는 TimeStampRenamePolicy를 만들어 보았습니다.

cos.jar 의 MultipartRequest를 확장한 확장자 체크 만들기.

Published on: 2011. 2. 15. 09:36 by louis.dev

그동안 파일 업로드를 할때 apache의 commons-fileupload를 사용하거나, oreilly의 MultipartRequest를 통해 업로드를 해왔습니다. 사실은 commons-fileupload 보다 MultipartRequest를 사용하는 것을 더 선호 했습니다.

왜냐!!

더 쉽기 때문이죠..^^;;

MultipartRequest를 통해 파일 업로드를 하려면, 단순히 MultipartRequest 인스턴스를 생성하는 것만으로도 파일업로드가 진행이 되었습니다.

이렇게 쉽게 파일 업로드를 하는 MultipartRequest에도 문제점은 가지고 있었으니.. 그것은 바로, 파일업로드를 진행하기 전, 파일의 확장자를 체크하지 못한다는 점입니다. 보안상 이슈로 인해 jsp, php, asp와 같은 파일들은 업로드 하지 못하게 막아야 하는데 ( 업로드 한후 업로드 한 파일을 실행하여 서버의 정보나 기타 다른 보안내용을 가져갈 수 있기 때문입니다..). 물론 스크립트 단에서 스크립트를 통해 확장자를 제어 하는 방법이 존재 하긴 하지만, 스크립트는 언제나 우회 가능 하기 때문에, 결국은 Backend에서 확장자를 체크하고, 확장자가 유효한지, 유효하지 않은지에 따라 업로드 가능 유무를 체크 해야 합니다.

이런 점을 들어, MultipartRequest를 개조하여 CheckExtentionMultipartRequest 를 만들어 보았습니다. 파일 Validation을 하는 것과 동시에, 소스 코드 리펙토링을 진행하여 코드를 작성해 보았습니다.

소스코드를 보다 보니 대부분의 Collection을 Vector로 작성을 하였는데, 아마 synchronized 때문에 Vector를 사용한 것으로 보여집니다. 하지만 자바 권고 안은 synchronized 가 걸린 Collection은
 
 Collections.synchronizedMap( new HashMap() )

을 통해 생성하라고 권고 하고 있기 때문에 Vector로 구현된 내용을 위와 같이 수정하였습니다.


CheckExtentionMultipartRequest

package net.tutorial;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Vector;

import javax.servlet.http.HttpServletRequest;

import net.daum.info.exception.InvalidFileExtensionException;

import com.oreilly.servlet.multipart.FilePart;
import com.oreilly.servlet.multipart.FileRenamePolicy;
import com.oreilly.servlet.multipart.MultipartParser;
import com.oreilly.servlet.multipart.ParamPart;
import com.oreilly.servlet.multipart.Part;

@SuppressWarnings("unchecked")
public class CheckExtentionMultipartRequest {
	//파라미터 저장 Map
	protected Map<Object,Object> parameters = Collections.synchronizedMap( new HashMap() ); 
	//file 저장 Map
	protected Map<Object,Object> files = Collections.synchronizedMap( new HashMap() ); 

	//기존의 파라미터들은 동일하고, String[] extentsion이 추가 됨.
	//.(컴마)를 제외한 제외하려는 확장자를 array 형태로 넘기면 됨(ex - {"php","jsp","asp"} )
	public CheckExtentionMultipartRequest(HttpServletRequest request,String saveDirectory, int maxPostSize, String encoding,FileRenamePolicy policy, String[] extensions) throws Exception {
		// Sanity check values
		if (request == null)
			throw new IllegalArgumentException("request cannot be null");
		if (saveDirectory == null)
			throw new IllegalArgumentException("saveDirectory cannot be null");
		if (maxPostSize <= 0) {
			throw new IllegalArgumentException("maxPostSize must be positive");
		}

		// Save the dir
		File dir = new File(saveDirectory);

		// 디렉토리 인지 체크
		if (!dir.isDirectory())
			throw new IllegalArgumentException("Not a directory: "+ saveDirectory);

		// write 할수 있는 디렉토리인지 체크
		if (!dir.canWrite())
			throw new IllegalArgumentException("Not writable: " + saveDirectory);

		MultipartParser parser = new MultipartParser(request, maxPostSize, true, true, encoding);

		if (request.getQueryString() != null) {
			// HttpUtil이 Deprecated 되었기 때문에 다른방식으로 작성						Map<String,Object> queryParameters = Collections.synchronizedMap( request.getParameterMap() );
			Iterator queryParameterNames = queryParameters.keySet().iterator();
			while( queryParameterNames.hasNext() ) {
				Object paramName = queryParameterNames.next();
				String[] values = ( String[] ) queryParameters.get( paramName );
				List<Object> newValues = Collections.synchronizedList( new ArrayList<Object>() );
				for( String value : values ) {
					newValues.add( value );
				}
				parameters.put(paramName, newValues ); 	
			}
		}

		Part part;
		while ((part = parser.readNextPart()) != null) {
			String name = part.getName();
			if (name == null) {
				throw new IOException("Malformed input: parameter name missing (known Opera 7 bug)");
			}
			if (part.isParam()) {
				// It's a parameter part, add it to the vector of values
				ParamPart paramPart = (ParamPart) part;
				String value = paramPart.getStringValue();
				List<Object> existingValues = (List<Object>) parameters.get(name);
				if (existingValues == null) {
					existingValues = new Vector();
					parameters.put(name, existingValues);
				}
				existingValues.add( value );
			} else if ( part.isFile() ) {
				// It's a file part
				FilePart filePart = (FilePart) part;
				String fileName = filePart.getFileName();
				if (fileName != null) {
					//file 확장자를 validation 합니다.
					//만약 유효하지 않은 확장자라면 InvalidFileExtensionException을 throw 합니다.
					if( !isValidFileExtension(fileName, extensions)) {
						throw new InvalidFileExtensionException( "Invalid File Extension" );
					}
					else {
						filePart.setRenamePolicy(policy); // null policy is OK
						filePart.writeTo(dir);
						files.put(name,
								new UploadedFile(dir.toString(), filePart
										.getFileName(), fileName, filePart
										.getContentType()));	
					}
					
				} else {
					// The field did not contain a file
					files.put(name, new UploadedFile(null, null, null, null));
				}
			}
		}
	}

	public Iterator<Object> getParameterNames() {
		return parameters.keySet().iterator();
	}

	public Iterator<Object> getFileNames() {
		return files.keySet().iterator();
	}

	public String getParameter(String name) {
		try {
			List<Object> values = (List<Object>) parameters.get(name);
			if (values == null || values.size() == 0) {
				return null;
			}
			String value = (String) values.get(values.size() - 1);
			return value;
		} catch (Exception e) {
			return null;
		}
	}

	public Object[] getParameterValues(String name) {
		try {
			List<Object> values = (List<Object>) parameters.get(name);
			if (values == null || values.size() == 0) {
				return null;
			}
			return values.toArray();
		} catch (Exception e) {
			return null;
		}
	}

	public String getFilesystemName(String name) {
		try {
			UploadedFile file = (UploadedFile) files.get(name);
			return file.getFilesystemName(); // may be null
		} catch (Exception e) {
			return null;
		}
	}

	public String getOriginalFileName(String name) {
		try {
			UploadedFile file = (UploadedFile) files.get(name);
			return file.getOriginalFileName(); // may be null
		} catch (Exception e) {
			return null;
		}
	}

	public String getContentType(String name) {
		try {
			UploadedFile file = (UploadedFile) files.get(name);
			return file.getContentType(); // may be null
		} catch (Exception e) {
			return null;
		}
	}

	public File getFile(String name) {
		try {
			UploadedFile file = (UploadedFile) files.get(name);
			return file.getFile(); // may be null
		} catch (Exception e) {
			return null;
		}
	}
	
	public boolean isValidFileExtension( String fileName ,String[] extensions) throws Exception{
		boolean result = false;
		if( fileName != null && !"".equals( fileName ) && !isContainFileExtension(fileName, extensions)) {
			result = true;
		}
		else {
			result = false;
		}
		return result;
	}
	private boolean isContainFileExtension( String fileName, String[] extensions ) throws Exception{
		boolean result = false;
		String fileExtension = getFileExtension( fileName );
		for( String ex : extensions ) {
			if( fileExtension.equals( ex ) ) {
				result = true;
				break;
			}
		}
		return result;
	}
	private String getFileExtension( String fileName ) throws Exception{
		String fileExtension = "";
		if( fileName != null && !"".equals( fileName )) {
			if( fileName.lastIndexOf( "." ) != -1){
				fileExtension = fileName.toLowerCase().substring( fileName.lastIndexOf( "." ) + 1, fileName.length() );
			}
			else {
				fileExtension = "";
			}
		}else{
			fileExtension = "";
		}
		return fileExtension;
	}
}
class UploadedFile {

	private String dir;
	private String filename;
	private String original;
	private String type;

	UploadedFile(String dir, String filename, String original, String type) {
		this.dir = dir;
		this.filename = filename;
		this.original = original;
		this.type = type;
	}

	public String getContentType() {
		return type;
	}

	public String getFilesystemName() {
		return filename;
	}

	public String getOriginalFileName() {
		return original;
	}

	public File getFile() {
		if (dir == null || filename == null) {
			return null;
		} else {
			return new File(dir + File.separator + filename);
		}
	}
}


위와 같이 작성하면 됩니다. InvalidFileExtensionException은 간단하게 다음과 같이 작성하시면 됩니다.

package net.tutorial.exception

@SuppressWarnings("serial")
public class InvalidFileExtensionException extends Exception {

	public InvalidFileExtensionException(String message) {
		super(message);
	}

}


실질 적으로 사용하는 방법은 다음과 같이 사용하시면 됩니다.
ExtensionCheckMultipartRequest multipartReqeust = null;
	try {
		String ex = {"php","jsp","asp"};
		multipartReqeust = new ExtensionCheckMultipartRequest(request, "파일업로드 패스", 5 * 1024 * 1024, "utf-8", new DefaultFileRenamePolicy(), ex);
	}catch ( InvalidFileExtensionException e) {
		e.printStackTrace();
		//TODO
	}

JAVA - 정규식을 이용한 문자열에서 HTML 태그 제거하기

Published on: 2010. 9. 29. 19:28 by louis.dev

String textWithoutTag = text.replaceAll("<(/)?([a-zA-Z]*)(\\s[a-zA-Z]*=[^>]*)?(\\s)*(/)?>", "");