FtpWebRequest 타입을 이용해 FTP 파일 업로드
사실 아래의 글에서 잘 설명하고 있지만,
How to: Upload Files with FTP
; https://learn.microsoft.com/en-us/dotnet/framework/network-programming/how-to-upload-files-with-ftp
좀 더 부가설명을 해보겠습니다.
우선, FtpWebRequest 타입은 기본값으로 Passive 모드의 연결을 합니다. 그래서 Active 모드만 가능하도록 설정된 FTP 서버로 접속했을 때는 다음과 같은 오류가 발생합니다.
// 192.168.0.22번 서버로 연결 시도를 하는 상황
System.Net.WebException: The remote server returned an error: 227 Entering Passive Mode (192,168,0,22,200,90).
. ---> System.Net.Sockets.SocketException: A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond 192.168.0.22:51290
at System.Net.Sockets.Socket.DoConnect(EndPoint endPointSnapshot, SocketAddress socketAddress)
at System.Net.Sockets.Socket.Connect(EndPoint remoteEP)
at System.Net.FtpControlStream.QueueOrCreateDataConection(PipelineEntry entry, ResponseDescription response, Boolean timeout, Stream& stream, Boolean& isSocketReady)
at System.Net.FtpControlStream.PipelineCallback(PipelineEntry entry, ResponseDescription response, Boolean timeout, Stream& stream)
at System.Net.CommandStream.PostReadCommandProcessing(Stream& stream)
at System.Net.CommandStream.PostSendCommandProcessing(Stream& stream)
at System.Net.CommandStream.ContinueCommandPipeline()
at System.Net.FtpWebRequest.TimedSubmitRequestHelper(Boolean async)
at System.Net.FtpWebRequest.SubmitRequest(Boolean async)
--- End of inner exception stack trace ---
at System.Net.FtpWebRequest.CheckError()
at System.Net.FtpWebRequest.SyncRequestCallback(Object obj)
at System.IO.Stream.Close()
at System.Net.ConnectionPool.Destroy(PooledStream pooledStream)
at System.Net.ConnectionPool.PutConnection(PooledStream pooledStream, Object owningObject, Int32 creationTimeout, Boolean canReuse)
at System.Net.FtpWebRequest.FinishRequestStage(RequestStage stage)
at System.Net.FtpWebRequest.GetRequestStream()
at FtpUpload.Program.Main(String[] args) in c:\...\Program.cs:line 40
이런 경우, 명시적으로 UsePassive 속성 값을 false로 설정해 주시면 문제가 해결됩니다.
FtpWebRequest request = WebRequest.Create(uploadPath) as FtpWebRequest;
Console.WriteLine("Passive: " + request.UsePassive); // 기본값: true
request.UsePassive = false;
접속하는 계정 권한으로 서버에 쓰기 권한이 없을 때는 업로드 시에 이런 오류가 발생합니다.
System.Net.WebException: The remote server returned an error: (550) File unavailable (e.g., file not found, no access).
at System.Net.FtpWebRequest.CheckError()
at System.Net.FtpWebRequest.SyncRequestCallback(Object obj)
at System.IO.Stream.Close()
at System.Net.ConnectionPool.Destroy(PooledStream pooledStream)
at System.Net.ConnectionPool.PutConnection(PooledStream pooledStream, Object owningObject, Int32 creationTimeout, Boolean canReuse)
at System.Net.FtpWebRequest.FinishRequestStage(RequestStage stage)
at System.Net.FtpWebRequest.GetRequestStream()
at FtpUpload.Program.Main(String[] args) in c:\...\Program.cs:line 40
당연히 서버에 해당 계정으로 쓰기 권한을 주어야 문제가 해결됩니다.
아래와 같이 희한한 오류가 발생하는 경우가 있습니다.
System.Net.WebException was unhandled
_HResult=-2146233079
_message=The remote server returned an error: (553) File name not allowed.
HResult=-2146233079
IsTransient=false
Message=The remote server returned an error: (553) File name not allowed.
Source=System
StackTrace:
at System.Net.FtpWebRequest.CheckError()
at System.Net.FtpWebRequest.SyncRequestCallback(Object obj)
at System.IO.Stream.Close()
at System.Net.ConnectionPool.Destroy(PooledStream pooledStream)
at System.Net.ConnectionPool.PutConnection(PooledStream pooledStream, Object owningObject, Int32 creationTimeout, Boolean canReuse)
at System.Net.FtpWebRequest.FinishRequestStage(RequestStage stage)
at System.Net.FtpWebRequest.GetRequestStream()
at FtpUpload.Program.Main(String[] args) in c:\...\Program.cs:line 40
InnerException:
검색해 보면 다음의 글이 나오는데요.
Can't connect to FTP: (553) File name not allowed
; http://stackoverflow.com/questions/9418404/cant-connect-to-ftp-553-file-name-not-allowed
원인은 로그인 계정과 연관된 기본 디렉터리(default directory) 경로까지 포함한 경우에 이런 현상이 발생합니다. 예를 들어,
FileZilla와 같은 프로그램으로 FTP 서버 접속 시에 다음과 같은 상태 정보가 나타나는 데,
Status: Resolving address of www.jennifersoft.co.kr
Status: Connecting to 192.168.0.22:21...
Status: Connection established, waiting for welcome message...
Status: Server does not support non-ASCII characters.
Status: Connected
Status: Retrieving directory listing...
Status: Directory listing of "/home/mydir" successful
Status: Retrieving directory listing of "/home/mydir/appupload"...
Status: Calculating timezone offset of server...
Status: Timezone offsets: Server: 0 seconds. Local: 32400 seconds. Difference: 32400 seconds.
Status: Directory listing of "/home/mydir/appupload" successful
...[이하 생략]...
보는 바와 같이 로그인과 동시에 기본 디렉터리("/home/mydir")로 이동하고 있습니다. 바로 이 기본 디렉터리 경로를 FTP에 전달해 주면 안됩니다. 예를 들어, 경로가 "/home/mydir/appupload"라고 가정했을 때,
string uploadPath = "ftp://192.168.0.22/home/mydir/appupload/test.txt";
FtpWebRequest request = WebRequest.Create(uploadPath) as FtpWebRequest;
라고 전달하면 "(553) File name not allowed." 오류가 발생합니다. 따라서 기본 디렉터리를 빼고 다음과 같이 지정해야만 정상적으로 업로드가 됩니다.
string uploadPath = "ftp://192.168.0.22/appupload/test.txt";
FtpWebRequest request = WebRequest.Create(uploadPath) as FtpWebRequest;
마지막으로, 제가 테스트한 FTP 업로드 예제 코드입니다. (
첨부 파일은 이 코드를 포함합니다.)
using System;
using System.IO;
using System.Net;
class Program
{
static int Main(string[] args)
{
try
{
if (args.Length < 5)
{
Console.WriteLine("FtpUpload.exe [ftpserver] [userid] [userpass] [serverpath] [filename]");
return 1;
}
string serverAddress = args[0];
string userId = args[1];
string userPass = args[2];
string dir = args[3];
string filePath = args[4];
string fileName = Path.GetFileName(filePath);
string uploadPath = "ftp://" + serverAddress + dir + fileName;
FtpWebRequest request = WebRequest.Create(uploadPath) as FtpWebRequest;
Console.WriteLine("Passive: " + request.UsePassive);
request.Method = WebRequestMethods.Ftp.UploadFile;
request.Credentials = new NetworkCredential(userId, userPass);
byte[] fileContents = File.ReadAllBytes(filePath);
Stream requestStream = request.GetRequestStream();
request.ContentLength = fileContents.Length;
requestStream.Write(fileContents, 0, fileContents.Length);
requestStream.Close();
FtpWebResponse response = request.GetResponse() as FtpWebResponse;
Stream responseStream = response.GetResponseStream();
StreamReader reader = new StreamReader(responseStream);
Console.WriteLine(reader.ReadToEnd());
Console.WriteLine("Complete, status {0}, length {1}", response.StatusDescription, response.ContentLength);
response.Close();
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
return 1;
}
return 0;
}
}
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]