레이블이 asp인 게시물을 표시합니다. 모든 게시물 표시
레이블이 asp인 게시물을 표시합니다. 모든 게시물 표시

2020년 5월 15일 금요일

Classic ASP로 네이버 단축주소 개발하기..

언어가 오래되서리...

갈아타지도 못하고...

편하게 Java 쓰고 싶뜨아....

기존에 bitly 쓰다가, 이 쉐리들이 API를 v3에서 v4로 업그레이드 하면서

기존 서비스를 완전히 먹통이 되게 함... (뭐 요딴놈들이 다 있나 싶다.. 무료 사용자라 무시하냐!)

v4로 업그레이드 하려는데.. 잘 안됨...(뭐든 시작이 이렇구만..)

만사가 귀찮아진 나는.. 또 머리를 굴렸다!!!

네이버 단축 주소 쓰자.... (따...딱히 자본력에 굴복한게 아니라는!!!)

어차피 xml http 통신만 구현하면 되고

기존에 xml 기반이던 것을 json 통신 기반으로 변경하면 됨.

소스 요약...

  1. Function fnNaverShorten(longURL)
  2.     Response.ContentType = "application/json"
  3.     reqAPIUrl = "https://openapi.naver.com/v1/util/shorturl?url=" & longURL
  4.     set xmlhttp = CreateObject("MSXML2.ServerXMLHTTP")  
  5.     xmlhttp.open "POST", reqAPIUrl, false
  6.     xmlhttp.setRequestHeader "content-type","application/x-www-form-urlencoded; charset=UTF-8"
  7.     xmlhttp.setRequestHeader "X-Naver-Client-Id", naverShortenClientID
  8.     xmlhttp.setRequestHeader "X-Naver-Client-Secret", naverShortenClientSecret
  9.     xmlhttp.send
  10.     if Request.QueryString("dbug") = "T" then Response.Write(reqAPIUrl)
  11.     fnNaverShorten = xmlhttp.responseText
  12.     set xmlhttp = nothing
  13. End Function

해설...
0. 일단.. 네이버개발자 센터에서 사전 준비 사항을 챙긴다!! (이것저것 생성 및 ID / 비번)
1. 난 그냥.. 컨텐츠 유형은 당연히 "application/json"
2. 뭐 너무 평범한 Classic ASP용 xml 통신 소스라.. 설명이 필요한지...
3. 발급한 ClientID와 ClientSecret를 잘 넣기만 해라..
4. 받는 Page에서 json 파싱은 알아서 해라.
5. 설명 귀찮다.. 끗...







2018년 7월 23일 월요일

오늘의 삽질 일기... ASP에서 json을 POST 로 넘겼을때 처리....

소요시간 : 6시간...

문제 사항 :

1. ASP 환경에서 json을 textarea에 담아서 POST로 넘긴다음 처리 할려고 했으나..

2. json String을 Parse 하는 부분에서 계속 오류...

3. ASP에서 사용되는 라이브러리를 변경하여 여러 방법으로 테스트 해봤으나.. 계속 오류...

4. POST로 받은 json String 을 Replace 해봤으나.. 계속 오류..

5. Raw Text 형태로 출력해 보면.. 정상적으로 값이 잘 보임...

6. 예전에 특수문자 Escape 문제가 생각나서 시도했으나.. 여전히 안됨..

7. 하다가.. 그냥.. jquery로 처리를 해보자 하는 마음에... javascript 변수에 받아서 출력해 봄.

8. 헉!!!! 더블쿼테이션(")  이 HTML 코드(")로 변경되어 넘어와 있네?

9. 더블쿼테이션(")을 변환하는 소스를 넣으니.. 잘된다..

10. 이놈의 삽질은 도데체 언제까지 해야 하나...

2018년 4월 6일 금요일

ASP로 단축주소 생성하자.

전 회사에서까진 Java 개발했는데..

점점 ASP의 입지 및 자료들이 줄게 되어... 개발 기록차 남김..

ASP가 그래도 간단하고 편하긴 하다..

google의 단축주소 api를 사용하려고 했으나, 서비스를 중단할 예정에 FireBase로 이전한다고 함..

FireBase는 함 둘러봤는데 뭐가 뭔지 모르겠음..ㅎㅎ

그냥 간단한.. Bitly 로 잽싸게 결정! 코드는 아래와 같다.

  1. <%
  2. Function fnBitlyUrl(longURL)
  3.     token = "bitly에서 발급받은 토큰값"
  4.     url = "https://api-ssl.bitly.com/v3/shorten?access_token="&token&"&longUrl=" & longURL & "&format=txt"
  5.    
  6.     set xmlhttp = CreateObject("MSXML2.ServerXMLHTTP")
  7.     xmlhttp.open "GET", url, false
  8.     xmlhttp.send ""    
  9.     fnBitlyUrl = xmlhttp.responseText
  10.     set xmlhttp = nothing
  11. END FUNCTION
  12. longURL = "http://www.lifebook.com" '변환하고 싶은 주소'
  13. response.write fnBitlyUrl(longURL)
  14. %>

적당히 입맛에 맞게 쓰길..

2017년 11월 22일 수요일

국세청 사이트에서 사업자 번호로 사업자 상태 조회하는 API 개발

0. 회사 ERP에 거래업체를 등록할 때마다 매번 국세청 사이트에 접속해서 사업자 번호로 사업자 상태를 조회해서 같이 입력을 해줘야 함

1. 국세청 사이트는 IE 전용이라 날 귀찮게 함.

2. 참고 또 참고 했지만, 역시 결국 귀찮음.

3. 에라이 개발해보자!!

4. 일단 있는거 있음 날로 먹기로 결심

5. 절라 구글링 해도 안나오거나, 뭔 놈의 유료로 파는 것만 발견.

6. 국세청 사이트의 해당 서비스를 분석

7. 결국은 서블릿 호출 + XML 전문통신

8. 간단하게 하기 위해서 ASP로 개발. (Java도 마찬가지 임.. 귀찮아서 개발 안함.)

9. 이하 소스

10. 퍼갈땐 출처 좀 밝혀 주길..(혹시 알어... 출세할지.. 불펌하면.. 혼내줄꺼임!)

11. git 에도 추가 (https://github.com/jaymz9634/retcompanystatus)

--------------------------------------------------------------------
2020-03-03 내용 추가:

국세청에서 과도한 접근에 대한 금지를 해놨음.

자체 테스트 해봤을때 한번에 30건 정도 조회하고 1분 쉬었다가 요청하면 문제없이 처리 됨을 확인.

내 경우에는 java로 구현해서 thread.sleep 으로 30건 마다 1분씩 쉬게해서 처리 완료.

<%
Session.CodePage = 949
Response.Charset = "euc-kr" '결과가 한글이기에 안 깨지게...'
'On Error Resume Next'

txprDscmNo = Request("txprDscmNo")

xmldata = xmldata & "<map id='ATTABZAA001R08'><pubcUserNo/><mobYn>N</mobYn><inqrTrgtClCd>1</inqrTrgtClCd><txprDscmNo>"&txprDscmNo&"</txprDscmNo><dongCode>05</dongCode><psbSearch>Y</psbSearch><map id='userReqInfoVO'/></map>"

set api = createobject("msxml2.serverXmlhttp")
api.open "POST", "https://teht.hometax.go.kr/wqAction.do?actionId=ATTABZAA001R08&screenId=UTEABAAA13&popupYn=false&realScreenId=", false 
api.setRequestHeader "content-type","text/xml; charset=utf-8"
api.send xmldata

if api.status = 200 Then
    Response.ContentType="text/xml"
    Response.Write(api.ResponseText)
else
    Response.Write("[statusCode : "&api.status&"]" & "<br/>")
    Response.Write("<hr/>")
    Response.Write(xmldata & "<br/>")
    Response.Write("<hr/>")
    Response.Write(api.responseText & "<br/>")'
End if

If Err.number <> 0 Then     '오류 발생 시 이 부분 실행
    Response.Write "<b3>" & Err.Source & "<hr noshade></h3>"
    Response.Write "오류 번호 : " & Err.number & "<br>"
    Response.Write "내용 : " & Err.Description & "<br>"
End If

Set api = nothing
%>

2016년 7월 7일 목요일

Classic ASP에서 solr 검색 값 Parsing 하기

검색 서버는 알아서들 구축 (solr 이젠 쉽죠?)

현재 개발환경 IIS 7.5

ASP는 오래된거에 비해서 야매 같은 기술들이 많아서리..

이번 건도.. 뭐가 정확한지 잘 모르겠으나..

일단 내 경험에서 되는걸로..


'검색서버에서 제품 정보 읽어서 XML로 저장하기..' Response.Buffer = true Session.CodePage = 949 상품코드 = "상품코드" Set objXmlHttp = server.CreateObject("Msxml2.ServerXMLHTTP.3.0") objXmlHttp.open "POST", "http://검색서버주소/solr/코어이름/select?q=상품코드:"&상품코드&"&indent=true", false
objXmlHttp.setRequestHeader "Content-Type", "application/x-www-form-urlencoded" objXmlHttp.send strResponseText = objXmlHttp.responseText Set objXmlHttp = Nothing
Dim xml: Set xml = Server.CreateObject("MSXML2.DOMDocument.3.0") xml.LoadXml(strResponseText)
Dim sResult: sResult = ""
Dim node For Each node in xml.selectSingleNode("/response/result/doc").childNodes 'sResult = sResult & node.xml' Response.Write(node.getattribute("name")) Response.Write("::") Response.Write(node.text) Response.Write(Chr(10)&Chr(13)) Next


Response.End()

여기까지 소스.

간단한 설명..

통신하는거야.. 뭐 보통 알고 있는 Msxml2.ServerXMLHTTP.3.0 사용함

의외로 검색서버가 UTF-8로 동작해서 당근

Session.CodePage = 65001로 했으나 의외로 깨짐..

그래서 949로 변경했더니 잘 됨.

문제는 parsing인데.. 처음에 구한 소스는 XMLDOM으로 하게 했음..

근데.. 안됨.. 아마 서버에 일반적으로 설치되는 컴포넌트가 아닌거 같다는 생각이 듬..

그래서 변경..

MSXML2.DOMDocument.3.0 일단 이게 잘 됨..

API 사이트는

https://msdn.microsoft.com/en-us/library/ms764730(VS.85).aspx

보통 여러 method 안쓸테니.. 그냥.. 속성 값과 node의 값 읽어오는 예제로

훈훈한 마무리..

끗~!

ASP용 코드 정리기

ASP용 코드 정리기

구하기 힘들어서 백업으로 게시함.

https://sites.google.com/site/jaymz9634/Home/aspyongkodeujeonglipeulogeulaem/RSFSimple.7z

2016년 6월 23일 목요일

Classic ASP에서 Microsoft VBScript 컴파일 오류 오류 '800a03f2' 발생시..

Microsoft VBScript 컴파일 오류 오류 '800a03f2'
식별자가 필요합니다.
~~~~~~~.asp, 줄 30


대부분 오타가 문제지만. 그래도 이상이 없다면.. 예약어를 사용한게 아닌지 확인해 보자..

Function fnChkFieldType( type )

End Function

위에서 type 이라는 예약어를 사용해서 문제 발생..

ASP 페이징 구현 관련..

"Recordset.GetRows() + Recordset.Move()" this is fastest way without using stored procedures.

프로시저 없을시엔 getrows + move 가 가장 성능이 좋다고 함..

http://databases.aspfaq.com/database/how-do-i-page-through-a-recordset.html


레코드셋에서 어떻게 페이지를 나눌까?


레코드셋으로 사용자에게 "페이지"를 제공하는 것은 웹 페이지 디자인에서 아주 일반적인 업무이다. 이 것은 각 페이지당 10~50 혹은 그 이상의 고정 값의 줄들을 보여준다는 것이고, 사용자는 다음/이전 또는 페이지 번호를 하단에서 클릭한다는 것이다.

여전에, 이 본문에는 세개의 예제가 있었다. 하나는 ASP로만, 두개는 저장 프로시저로 다른 접근을 했었다.

Thanks to Chris Hohmann, Anith Sen, Steve Kass, David Portas, Uri Dumant — and, most recently, Brynn and Bob Barrows — for providing me the ammunition and motivation to re-write this article.(저 쉐리들 땜에 다시 글을 작성하게 되서 고맙다는 거임..)
이제는, 열개의 다른 기술들을 포함하였다. 또한 하나씩 설명한 뒤, (결과에서 볼 수 있는)내 테스트들에서 가장 성능이 좋은 것을 보여 줄 것이고, 모든 예제들(과 테스트한 코드)은 다운로드를 제공한다.

(알파벳 단위를 사용하여 데이터 페이징을 하는 기술에 관심이 있다면..Article #2352을 참고하라.)




"페이지 당 줄 수" 문제에 대한 접근방식에는 크게 두가지가 있다: ASP에서의 레코드셋 사용과, 저장 프로시저 사용이다. 두가지 이유로 각각 나눠볼 것이다:(1)Access에서도 사용될 수 있는(WITH (NOLOCK) 힌트를 제거한 후) ASP에서 레코드 셋을 사용하는 코드; 그리고 (2) 일반적으로 사용하는 #include file을 사용할 것이고, 각각의 두가지 경우에 대해서 충분히 실행하고 분리해서 소개해 보겠다.

유콘(Yukon)에서, 새로운 방법으로 이 경우들을 제어해 볼것이다; TOP을 구문에 넣을 것이고, 대량의 자료에서 줄을 "부분적으로" 선택하게 하는 새로운 순위 기능은 훨 씬 쉽게 해 줄 것이다. 현재, SQL 서버 7.0이나 2000을 사용했을 때, 최적화 솔루션 보다 더 작은 부하를 주고 있다.

Recordset Methods

각각의 접근방법에 들어가기 전에, 설정을 하자. 우선 테이블이 필요하다:

CREATE TABLE SampleCDs
(
    ArtistName VARCHAR(64),
    Title VARCHAR(64),
    PRIMARY KEY (ArtistName, Title)
)
GO

채워넣을 데이터도 필요할 것이다. 테이블 생성시 사용한 스크립트는 써놨고, 내 CD 모음집을 넣어보자, here (73 KB).

다음은, ASP 환경에 대한 설정이 필요할 것이다. ASP 페이지들 대부분의 기능들은 일반적인 방법으로 제어 할 수 있고, 우리는 #include files의 장점을 사용할 것이다. 첫번째 파일은 inc.asp 라는 파일을 만들것이고, 연결정보를 갖고 있고 연결 객체(connection object)로 접속한다(물론 HTML 테이블에 사용되는 단일 형식의 선언이다):

<%
    Set Conn = CreateObject("ADODB.Connection")
    Conn.Open "Provider=SQLOLEDB.1;" & _
        "Network=DBMSSOCN;" & _
        "Data Source=<server>;" & _
        "Initial Catalog=<databasename>;" & _
        "User ID=<username>;" & _
        "Password=<password>"
%>
<style>
.n { border-top:1px solid #999999 }
</style>

To run these individual pages, this should be the only file you have to modify.

The next page is called topRS.asp. It includes logic for obtaining total rowcount from the table, making sure the values passed in (for page number and number of rows per page) are valid, and setting up the dropdown we're going to use to navigate from page to page.

<%
    countSQL = "SELECT COUNT(*) FROM SampleCDs WITH (NOLOCK)"

    RowCnt = 0

    set rs = conn.execute(countSQL)

    if not rs.eof then
        RowCnt = clng(rs(0))
    end if

    if RowCnt = 0 then
    Response.Write "No rows found."
        Response.End
    end if

    PerPage = Trim(Request.QueryString("PerPage"))
    PageNum = Trim(Request.QueryString("PageNum"))

    If PerPage = "" or (len(PerPage)>0 and not isnumeric(PerPage)) Then _
        PerPage = 50

    If PageNum = "" or (len(PageNum)>0 and not isnumeric(PageNum)) Then _
        PageNum = 1

    PerPage = clng(PerPage)
    PageNum = clng(PageNum)

    PageCnt = RowCnt \ PerPage

    if RowCnt mod PerPage <> 0 then PageCnt = PageCnt + 1
    if PageNum < 1 Then PageNum = 1
    if PageNum > PageCnt Then PageNum = PageCnt

    url = Request.ServerVariables("SCRIPT_NAME")
    urlParts = split(url, "/")
    pageName = urlParts(ubound(urlParts))
%>
<script>
function go(p)
{
    if (p!='current')
    window.location.href = "<%=pageName%>?"
        +"PerPage=<%=PerPage%>&"
        +"PageNum="+p;
}
</script>
<%
    response.write RowCnt & " rows found. Showing " & _
        " page " & PageNum & " of " & PageCnt & "."

    response.write " <select onchange='go(this.value);'>"

for i = 1 to PageCnt
        link = i: s = ""
        if i = PageNum then link = "current": s=" SELECTED"
        response.write "<option value=" & link & s & ">"
        response.write "Page " & i
next
Response.Write "</select><p>"
%>

Finally, we have an #include file that cleans up, called foot.asp:

<%
    rs.close: set rs = nothing
    conn.close: set conn = nothing
%>

So, each of our code samples for recordset techniques will look like this:

<!--#include file=inc.asp-->
<!--#include file=topRS.asp-->
<%
    ' ... code ...
%>
<!--#include file=foot.asp-->

Recordset.AbsolutePage
    The first method we'll look at is the AbsolutePage method. This is probably the one you see most often; it uses an explicit ADODB.Recordset object, and properties like PageSize. Of the recordset methods, this one performed the worst in our limited testing (see <a href="<a href="http://databases.aspfaq.com/database/how-do-i-page-through-a-recordset.html#results">http://databases.aspfaq.com/database/how-do-i-page-through-a-recordset.html#results</a>" target="_blank" title="#results" style="color: rgb(51, 51, 153); padding-left: 2px; padding-right: 2px;">results</a>).

    <!--#include file=inc.asp-->
    <!--#include file=topRS.asp-->
    <%
        dataSQL = "SELECT ArtistName, Title FROM SampleCDs WITH (NOLOCK)"

        set rs = CreateObject("ADODB.Recordset")
        rs.PageSize = PerPage
        rs.CacheSize = PerPage

        ' 3 = adOpenStatic, 1 = adLockReadOnly, &H0001 = adCmdText
        rs.Open dataSQL, conn, 3, 1, &H0001

        if not rs.eof then

            response.write "<table border=0 cellpadding=5>"

            rs.AbsolutePage = PageNum

            x = 0
            do while x < PerPage and not rs.eof
               
                artist = rs(0)
                title = rs(1)

                if artist <> prevArtist then
                    prevArtist = artist
                    response.write "<tr><td class=n>"
                    response.write artist & "</td>"
                    response.write "<td class=n>"
                else
                    response.write "<tr><td> </td><td>"
                end if

                response.write title & "</td></tr>"

                x = x + 1

                rs.movenext
            loop

            response.write "</table>"
        else
            response.write "No rows found."
            response.end
        end if
    %>
    <!--#include file=foot.asp-->

    This code can be found in rsPage.asp.
Recordset.GetRows() + AbsolutePage
    Most people would probably assume that GetRows() would perform quite well, but it only performed slightly better than the AbsolutePage method described above. GetRows() converts a heavy recordset object into a lighter-weight array for local processing (see <a href="<a href="http://www.aspfaq.com/show.asp?id=2467">http://www.aspfaq.com/show.asp?id=2467</a>" target="_self" title="<a href="http://www.aspfaq.com/show.asp?id=2467">http://www.aspfaq.com/show.asp?id=2467</a>" style="color: rgb(51, 51, 153); padding-left: 2px; padding-right: 2px;">Article #2467</a> for more information); though, in order to reduce the size of the array, we use AbsolutePage and PageSize to move right to the relevant chunk of rows.

    <!--#include file=inc.asp-->
    <!--#include file=topRS.asp-->
    <%
        dataSQL = "SELECT ArtistName, Title FROM SampleCDs WITH (NOLOCK)"

        set rs = CreateObject("ADODB.Recordset")
        rs.PageSize = PerPage
        rs.CacheSize = PerPage

        ' 3 = adOpenStatic, 1 = adLockReadOnly, &H0001 = adCmdText
        rs.Open dataSQL, conn, 3, 1, &H0001

        if not rs.eof then    

            rs.AbsolutePage = PageNum

            Dim gr
            gr = rs.GetRows(PerPage)

            response.write "<table border=0 cellpadding=5>"

            for i = 0 to perpage - 1
               
                artist = gr(0, i)
                title = gr(1, i)

                if artist <> prevArtist then
                    prevArtist = artist
                    response.write "<tr><td class=n>"
                    response.write artist & "</td>"
                    response.write "<td class=n>"
                else
                    response.write "<tr><td> </td><td>"
                end if

                response.write title & "</td></tr>"

            next

            response.write "</table>"
        else
            response.write "No rows found."
            response.end
        end if
    %>
    <!--#include file=foot.asp-->

    This code can be found in rsGetRowsPage.asp.
Recordset.Move()
    The Recordset.Move() technique was the first example I ever posted in this article. In an attempt to eliminate the need for heavy recordset objects, I decided to try the Move() method to skip the first n rows in the resultset to start at the first row for the page we are interested in.

    <!--#include file=inc.asp-->
    <!--#include file=topRS.asp-->
    <%
    rstart = PerPage * Pagenum - (PerPage - 1)

        dataSQL = "SELECT ArtistName, Title FROM SampleCDs WITH (NOLOCK)"

        set rs = conn.execute(dataSQL)

        if not rs.eof then

            rs.move(rstart-1)

            response.write "<table border=0 cellpadding=5>"

            for x = 1 to PerPage

                if rs.eof then exit for

                artist = rs(0)
                title = rs(1)

                if artist <> prevArtist then
                    prevArtist = artist
                    response.write "<tr><td class=n>"
                    response.write artist & "</td>"
                    response.write "<td class=n>"
                else
                    response.write "<tr><td> </td><td>"
                end if

                response.write title & "</td></tr>"

                rs.movenext
            next

            response.write "</table>"
        else
            response.write "No rows found."
            response.end
        end if
    %>
    <!--#include file=foot.asp-->

    This code can be found in rsMove.asp.
Recordset.GetRows() + Recordset.Move()
    This method combines the effective move() technique, with GetRows() ability to accept a "chunk" as a parameter. Of the recordset technologies we tested, this performed best.

    <!--#include file=inc.asp-->
    <!--#include file=topRS.asp-->
    <%
        dataSQL = "SELECT ArtistName, Title FROM SampleCDs WITH (NOLOCK)"

        set rs = conn.execute(dataSQL)

        if not rs.eof then    

            Dim gr
            rstop = PerPage * PageNum
            rstart = rstop - (PerPage - 1)
            rs.move(rstart-1)

            if rstop > RowCnt - 1 then PerPage = (RowCnt + 1) - rstart
            gr = rs.GetRows(PerPage)

            response.write "<table border=0 cellpadding=5>"

            for i = 0 to perpage-1
               
                artist = gr(0, i)
                title = gr(1, i)

                if artist <> prevArtist then
                    prevArtist = artist
                    response.write "<tr><td class=n>"
                    response.write artist & "</td>"
                    response.write "<td class=n>"
                else
                    response.write "<tr><td> </td><td>"
                end if

                response.write title & "</td></tr>"

            next

            response.write "</table>"
        else
            response.write "No rows found."
            response.end
        end if
    %>
    <!--#include file=foot.asp-->

    This code can be found in rsGetRowsMove.asp.



Stored Procedure Methods

If you are using Access, you could probably enhance a paging solution above to use stored queries. However, stored queries are not quite as flexible and powerful as stored procedures in SQL Server. One of the main drawbacks of the recordset methods described above is that *all* the rows are requested from the database and sent over the network to the ASP page. It is only at the web server that the bulk of the resultset is discarded, and this can be quite a wasteful operation on larger tables.

I experimented with six different techniques in stored procedures to carve up a resultset into pages of user-defined row counts. A couple are my own design, one came from Chris Hohmann, and a couple were derived from conversations in the SQL Server newsgroups.

Like with the recordset methods, before we dive into the stored procedure techniques, we need to set up a few things. First will be the ASP pages. As before, we have inc.asp (for connection information) and foot.asp (for cleanup). Instead of topRS.asp, we have topSP.asp. This file is moderately different; the main difference being that we no longer need a query to determine total number of rows / pages, since we will get that from the stored procedure each time. Here is topSP.asp:

<%
    PerPage = Trim(Request.QueryString("PerPage"))
    PageNum = Trim(Request.QueryString("PageNum"))

    If PerPage = "" or (len(PerPage)>0 and not isnumeric(PerPage)) Then _
        PerPage = 50

    If PageNum = "" or (len(PageNum)>0 and not isnumeric(PageNum)) Then _
        PageNum = 1

    PerPage = clng(PerPage)
    PageNum = clng(PageNum)

    url = Request.ServerVariables("SCRIPT_NAME")
    urlParts = split(url, "/")
    pageName = urlParts(ubound(urlParts))
%>
<script>
function go(p)
{
    if (p!='current')
    window.location.href = "<%=pageName%>?"
        +"PerPage=<%=PerPage%>&"
        +"PageNum="+p;
}
</script>
<%
    spName = "SampleCDs_Paging_" & Mid(Left(pageName, Len(pageName)-4), 3, 32)
    dataSQL = "EXEC " & spName & " " & pageNum & ", " & perPage

    set rs = conn.execute(dataSQL)

    PageNum = rs(0)
    PageCnt = rs(1)
    RowCnt = rs(2)

    if RowCnt = 0 then
        response.write "No rows found."
        response.end
    end if

    response.write RowCnt & " rows found. Showing " & _
        " page " & PageNum & " of " & PageCnt & "."

    response.write " <select onchange='go(this.value);'>"

for i = 1 to PageCnt
        link = i: s = ""
        if i = PageNum then link = "current": s=" SELECTED"
        response.write "<option value=" & link & s & ">"
        response.write "Page " & i
next
Response.Write "</select><p>"
%>

Notice that I named the stored procedures in a manner consistent with the name of the ASP pages that call them, respectively. This way, we can use the name of the page to call the stored procedure, instead of hard-coding it into six different files.

Now, the ASP code used to call each stored procedure is identical. So for each method below, you will be asked to create an ASP file with a specific name, using the "common ASP code from above." That code is as follows:

<!--#include file=inc.asp-->
<!--#include file=topSP.asp-->
<%
    set rs = rs.NextRecordset()

    if not rs.eof then    

        Dim gr
        gr = rs.GetRows()

        rstop = PerPage - 1

        if rstop > ubound(gr, 2) then rstop = ubound(gr, 2)

        response.write "<table border=0 cellpadding=5>"

        for i = 0 to rstop
           
            artist = gr(0, i)
            title = gr(1, i)

            if artist <> prevArtist then
                prevArtist = artist
                response.write "<tr><td class=n>"
                response.write artist & "</td>"
                response.write "<td class=n>"
            else
                response.write "<tr><td> </td><td>"
            end if

            response.write title & "</td></tr>"

        next

        response.write "</table>"
    else
        response.write "No rows found."
        response.end
    end if

%>
<!--#include file=foot.asp-->

We use GetRows() here not because it has any particular impact on any of the methods we chose, but rather because we know that this is *usually* the most efficient method of displaying a resultset. Rather than get into another comparison for the different display techniques, I chose to just process all the stored procedures using GetRows() to eliminate that as a source for discrepancy during performance testing. We don't really need to use the extended capabilities above (AbsolutePage, PageSize, Move(), and the "chunk" parameter) because the stored procedure is already reducing the resultset to exactly the size of slice we want.

User-Defined Function
    A UDF is basically an encapsulation of code that returns a table or a scalar value. In this case, I created a function that returns an integer, representing the "rank" of a given artist/title combination, alphabetically, compared to the rest of the table. In individual testing, when viewing the execution plan in Query Analyzer, this method seemed to do quite well. However, it did not fare so well when put to a heavier test (see <a href="<a href="http://databases.aspfaq.com/database/how-do-i-page-through-a-recordset.html#results">http://databases.aspfaq.com/database/how-do-i-page-through-a-recordset.html#results</a>" target="_blank" title="#results" style="color: rgb(51, 51, 153); padding-left: 2px; padding-right: 2px;">results</a>). The run time roughly doubled when dealing with rows near the "end" of the table; a good indicator of a well-implemented paging solution is little or no difference between obtaining the first page and the last page.

    Here is the function code:

    CREATE FUNCTION dbo.ReturnCDRank
    (
        @ArtistName VARCHAR(64),
        @Title VARCHAR(64)
    )
    RETURNS INT
    AS
    BEGIN
        RETURN
        (
            SELECT
                COUNT(*)
            FROM
                SampleCDs WITH (NOLOCK)
            WHERE
                ArtistName + '~' + Title
                <= @ArtistName + '~' + @Title
        )
    END
    GO

    And here is the stored procedure code:

    CREATE PROCEDURE SampleCDs_Paging_UDF
        @pagenum INT = 1,
        @perpage INT = 50
    AS
    BEGIN
        SET NOCOUNT ON

        DECLARE
            @ubound INT,
            @lbound INT,
            @pages INT,
            @rows INT

        SELECT
            @rows = COUNT(*),
            @pages = COUNT(*) / @perpage
        FROM
            SampleCDs WITH (NOLOCK)

        IF @rows % @perpage != 0 SET @pages = @pages + 1
        IF @pagenum < 1 SET @pagenum = 1
        IF @pagenum > @pages SET @pagenum = @pages

        SET @ubound = @perpage * @pagenum
        SET @lbound = @ubound - (@perpage - 1)

        SELECT
            CurrentPage = @pagenum,
            TotalPages = @pages,
            TotalRows = @rows

        -- this method uses a user-defined function
        -- to rank the rows based on how many rows
        -- exist with "lower" string values

        SELECT
            ArtistName,
            Title
        FROM
            SampleCDs WITH (NOLOCK)
        WHERE
            dbo.ReturnCDRank(ArtistName, Title)
        BETWEEN
            @lbound AND @ubound
        ORDER BY
            ArtistName,
            Title
    END
    GO

    To run this page, create a file called spUDF.asp, and include the common ASP code from above.
     
Subquery / COUNT
    Another method I played with is a subquery with a count. This uses a COUNT of rows in a subquery where the values are compared to the outer table. Like the UDF, the performance of this solution was unacceptable, though the run time didn't change much at either end of the result set.

    Here is the stored procedure code:

    CREATE PROCEDURE SampleCDs_Paging_Subquery
        @pagenum INT = 1,
        @perpage INT = 50
    AS
    BEGIN
        SET NOCOUNT ON

        DECLARE
            @ubound INT,
            @lbound INT,
            @pages INT,
            @rows INT

        SELECT
            @rows = COUNT(*),
            @pages = COUNT(*) / @perpage
        FROM
            SampleCDs WITH (NOLOCK)

        IF @rows % @perpage != 0 SET @pages = @pages + 1
        IF @pagenum < 1 SET @pagenum = 1
        IF @pagenum > @pages SET @pagenum = @pages

        SET @ubound = @perpage * @pagenum
        SET @lbound = @ubound - (@perpage - 1)

        SELECT
            CurrentPage = @pagenum,
            TotalPages = @pages,
            TotalRows = @rows

        -- this method uses a COUNT subquery to
        -- peg a sliding range to the desired set
        -- of rows

        SELECT
            A.ArtistName,
            A.Title
        FROM
            SampleCDs A WITH (NOLOCK)
        WHERE
        (
            SELECT COUNT(*)
            FROM SampleCDs B WITH (NOLOCK)
            WHERE B.ArtistName+'~'+B.Title
            <= A.ArtistName+'~'+A.Title
        )
        BETWEEN
            @lbound AND @ubound
        ORDER BY
            A.ArtistName,
            A.Title
    END
    GO

    To run this page, create a file called spSubquery.asp, and include the common ASP code from above.
     
Self-Join / COUNT
    This technique is only a little bit different from the subquery / COUNT technique, using a self-join instead to determine the ranking of the relevant rows. It performed a little better overall, and surprisingly, was better individually on later pages than earlier pages.

    Here is the stored procedure code:

    CREATE PROCEDURE SampleCDs_Paging_Join
        @pagenum INT = 1,
        @perpage INT = 50
    AS
    BEGIN
        SET NOCOUNT ON

        DECLARE
            @ubound INT,
            @lbound INT,
            @pages INT,
            @rows INT

        SELECT
            @rows = COUNT(*),
            @pages = COUNT(*) / @perpage
        FROM
            SampleCDs WITH (NOLOCK)

        IF @rows % @perpage != 0 SET @pages = @pages + 1
        IF @pagenum < 1 SET @pagenum = 1
        IF @pagenum > @pages SET @pagenum = @pages

        SET @ubound = @perpage * @pagenum
        SET @lbound = @ubound - (@perpage - 1)

        SELECT
            CurrentPage = @pagenum,
            TotalPages = @pages,
            TotalRows = @rows

        -- this method uses an inner join and a
        -- having clause to move a sliding window
        -- to the desired set of rows

        SELECT
            A.ArtistName,
            A.Title
        FROM
            SampleCDs A WITH (NOLOCK)
            INNER JOIN SampleCDs B WITH (NOLOCK)
        ON
            A.ArtistName+'~'+A.Title >= B.ArtistName+'~'+B.Title
        GROUP BY
            A.ArtistName, A.Title
        HAVING
            COUNT(*) BETWEEN @lbound AND @ubound
        ORDER BY
            A.ArtistName,
            A.Title

    END
    GO

    To run this page, create a file called spJoin.asp, and include the common ASP code from above.
     
#Temp table
    This was the first stored procedure example I posted to this article, though it is cleaned up a bit now. The code inserts the entire base table into a #temp table with an IDENTITY column, then uses that column to determine "rank." Note that the behavior we see here is taken for granted; it is neither documented nor guaranteed to obey the ORDER BY clause when inserting the rows into the #temp table. The optimizer will insert the rows in whatever order it deems fit, which means that the values in the columns could certainly be out of order. Luckily, it just so happens that it works right so much more often than it fails, that it is virtually reliable. Still, it should be noted with an asterisk, as the behavior could change in future versions.

    Here is the stored procedure code:

    CREATE PROCEDURE SampleCDs_Paging_TempTable
        @pagenum INT = 1,
        @perpage INT = 50
    AS
    BEGIN
        SET NOCOUNT ON

        DECLARE
            @ubound INT,
            @lbound INT,
            @pages INT,
            @rows INT

        SELECT
            @rows = COUNT(*),
            @pages = COUNT(*) / @perpage
        FROM
            SampleCDs WITH (NOLOCK)

        IF @rows % @perpage != 0 SET @pages = @pages + 1
        IF @pagenum < 1 SET @pagenum = 1
        IF @pagenum > @pages SET @pagenum = @pages

        SET @ubound = @perpage * @pagenum
        SET @lbound = @ubound - (@perpage - 1)

        SELECT
            CurrentPage = @pagenum,
            TotalPages = @pages,
            TotalRows = @rows

        -- this method inserts rows into a temp
        -- table, and the identity helps us rank
        -- them and return only the desired rows

        CREATE TABLE #CDList
        (
            CDID INT IDENTITY(1,1)
            PRIMARY KEY CLUSTERED,
            ArtistName VARCHAR(64),
            Title VARCHAR(64)
        )
       
        -- behavior not guaranteed!
        INSERT #CDList
            SELECT
                ArtistName,
                Title
            FROM
                SampleCDs WITH (NOLOCK)
            ORDER BY
                ArtistName,
                Title

        SELECT
            ArtistName,
            Title
        FROM
            #CDList
        WHERE
            CDID
        BETWEEN
            @lbound AND @ubound
        ORDER BY
            ArtistName,
            Title

        DROP TABLE #CDList
    END
    GO

    To run this page, create a file called spTempTable.asp, and include the common ASP code from above.
     
Dynamic SQL
    This is basically the second stored procedure method I posted to this article (and not too long ago, I might add). It uses nested TOP commands to get the "last" set of rows from a larger set that is basically the first @perpage * @pagenum rows. So, if we had 10 rows per page, and we were on page 8, we would be asking for:

    SELECT TOP 10 rows FROM (SELECT TOP 80 rows ORDER BY ASC) ORDER BY DESC

    (I'm probably not explaining that very well; my only excuse is that it's late.)

    Then we wrap another query around it so we can still present the rows in ASCENDING order. The only reason we have to use dynamic SQL here is because the TOP command cannot take a variable. (See <a href="<a href="http://www.sommarskog.se/dynamic_sql.html">http://www.sommarskog.se/dynamic_sql.html</a>" target="_blank" title="<a href="http://www.sommarskog.se/dynamic_sql.html">http://www.sommarskog.se/dynamic_sql.html</a>" style="color: rgb(51, 51, 153); padding-left: 2px; padding-right: 2px;">this article</a> for more information on dynamic SQL.)

    Here is the stored procedure code:

    CREATE PROCEDURE SampleCDs_Paging_DynamicSQL
        @pagenum INT = 1,
        @perpage INT = 50
    AS
    BEGIN
        SET NOCOUNT ON

        DECLARE
            @ubound INT,
            @lbound INT,
            @pages INT,
            @rows INT,
            @lastPageDiff INT

        SELECT
            @rows = COUNT(*),
            @pages = COUNT(*) / @perpage
        FROM
            SampleCDs WITH (NOLOCK)

        IF @rows % @perpage != 0
            SET @pages = @pages + 1
        IF @rows % @perpage != 0 AND @pagenum = @pages
            SET @lastPageDiff = @perPage - (@rows % @perpage)
        IF @pagenum < 1 SET @pagenum = 1
        IF @pagenum > @pages SET @pagenum = @pages

        SET @ubound = @perpage * @pagenum
        SET @lbound = @ubound - (@perpage - 1)

        SELECT
            CurrentPage = @pagenum,
            TotalPages = @pages,
            TotalRows = @rows

        -- this method uses dynamic SQL (because TOP
        -- can't take a parameter in SQL Server 2000
        -- and lower) to retrieve a nested subquery

        DECLARE @sql NVARCHAR(1024)

        SET @sql = N'SELECT
                ArtistName,
                Title
            FROM
            (
                SELECT TOP '+RTRIM(@perpage - @lastPageDiff)
                    +' ArtistName,
                    Title
                FROM
                (
                    SELECT TOP '+RTRIM(@perpage * @pagenum)
                        +' ArtistName,
                        Title
                    FROM
                        SampleCDs WITH (NOLOCK)
                    ORDER BY
                        ArtistName+''~''+Title
                ) a
                ORDER BY
                    ArtistName+''~''+Title DESC
            ) b
            ORDER BY
                ArtistName+''~''+Title'

        EXEC sp_executeSQL @sql

    END
    GO

    To run this page, create a file called spTempTable.asp, and include the common ASP code from above.
     
RowCount
    This was Chris Hohmann's gem, and I only touched it up a little bit to fit this example. Basically, it uses SET ROWCOUNT to assign the *first* value from the current page to local variables. Then it sets ROWCOUNT again to the value representing the number of rows we want to see per page, and asks for all the rows >= the value of the local variables. Altogether genius, and it was the best performer of the day as well.

    Here is the stored procedure code:

    CREATE PROCEDURE SampleCDs_Paging_Rowcount
        @pagenum INT = 1,
        @perpage INT = 50
    AS
    BEGIN
        SET NOCOUNT ON

        DECLARE
            @ubound INT,
            @lbound INT,
            @pages INT,
            @rows INT

        SELECT
            @rows = COUNT(*),
            @pages = COUNT(*) / @perpage
        FROM
            SampleCDs WITH (NOLOCK)

        IF @rows % @perpage != 0 SET @pages = @pages + 1
        IF @pagenum < 1 SET @pagenum = 1
        IF @pagenum > @pages SET @pagenum = @pages

        SET @ubound = @perpage * @pagenum
        SET @lbound = @ubound - (@perpage - 1)

        SELECT
            CurrentPage = @pagenum,
            TotalPages = @pages,
            TotalRows = @rows

        -- this method determines the string values
        -- for the first desired row, then sets the
        -- rowcount to get it, plus the next n rows

        DECLARE @aname VARCHAR(64), @title VARCHAR(64)

        SET ROWCOUNT @lbound

        SELECT
            @aname = ArtistName,
            @title = Title
        FROM
            SampleCDs WITH (NOLOCK)
        ORDER BY
            ArtistName,
            Title

        SET ROWCOUNT @perPage

        SELECT
            ArtistName,
            Title
        FROM
            SampleCDs WITH (NOLOCK)
        WHERE
            ArtistName + '~' + Title
            >= @aname + '~' + @title
        ORDER BY
            ArtistName,
            Title

        SET ROWCOUNT 0
    END
    GO

    To run this page, create a file called spRowcount.asp, and include the common ASP code from above.



Results

I used a looping script to hit each of the 9 ASP pages 20 times. 10 times where it would get the first page, and 10 times where it would get the last page (in this case, I broke the results up 20 rows at a time, and asked for the last page (64)). Here is the ASP code used in the test:

<%

    numberOfTries = 10

    server.scripttimeout = 300000

    dim pn(9)

    pn(0) = "rsMove"
    pn(1) = "rsPage"
    pn(2) = "rsGetRowsMove"
    pn(3) = "rsGetRowsPage"
    pn(4) = "spRowcount"
    pn(5) = "spDynamicSQL"
    pn(6) = "spSubquery"
    pn(7) = "spJoin"
    pn(8) = "spUDF"
    pn(9) = "spTempTable"

    set xmlhttp = CreateObject("MSXML2.ServerXMLHTTP")

    originalClock = timer
    response.write originalClock & "<P>"

    for i = 0 to 9

        originalClock = timer
        for x = 1 to numberOfTries
            url = "http://www.aaronbertrand.com/pg/" & _
                pn(i) & ".asp"
            xmlhttp.open "GET", url, false
            xmlhttp.send ""
            result = xmlhttp.ResponseText
        next
        newclock = timer
        response.write pn(i) & ": " & newclock & _
            " (" & newclock - originalClock & ")<br>"
        originalClock = newCLock

        for x = 1 to numberOfTries
            url = "http://www.aaronbertrand.com/pg/" & _
                pn(i) & ".asp?PageNum=62&PerPage=20"
            xmlhttp.open "GET", url, false
            xmlhttp.send ""
            result = xmlhttp.ResponseText
        next
        newclock = timer
        response.write pn(i) & ": " & newclock & _
            " (" & newclock - originalClock & ")<br>"
        originalClock = newCLock
    next
    response.write "<P>" & NewClock
    set xmlhttp = nothing
%>

I assign the ResponseText to a result, even though it is never used, to make sure I simulate the drawing of data down to the browser; even though the rendering is missing, it makes the performance truer than never asking for the responseText.
브라우저에 데이터를 표시하는 것을 가정하기 위해서, 결과에 전혀 사용하지는 않았지만,  ResponseText(응답 텍스트)를 할당하였다; 렌더링시에 누락이 있긴 했지만, 응답 텍스트를 전혀 요청하지 않는 것 보다 더 성능이 좋았다.

Okay, enough with the chit-chat. Here are the results.
자, 잡담은 그만하고, 여기 결과값들이 있다.

MethodTime (seconds)
(Per page)
Time (seconds)
(Per page)
Average
Stored Procedure
Rowcount
0.0150000.0168750.015938
Recordset
GetRowsMove
0.0168750.0168750.016875
Recordset
Move
0.0250000.0175000.021250
Stored Procedure
DynamicSQL
0.0218750.0381250.030000
Recordset
GetRowsPage
0.0350000.0337500.034375
Recordset
Page
0.0375000.0337500.035625
Stored Procedure
TempTable
0.0506250.0443750.047500
Stored Procedure
Join
4.6987502.0181253.358438
Stored Procedure
Subquery
4.1918754.1975004.194688
Stored Procedure
UDF
3.3843756.4456254.915000

자, 보는대로다. 페이징 문제에 대한 10가지 접근방법이다. 더 나은 결과치(gospel?)을 얻고 싶다면, 당신의 환경에서 하나씩 시험해 보라. 당신의 하드웨어나, 네트워트 용량, 원격지 데이터베이스에서의 페이지 테스트 여부 등에 따라서, 당신의 결과치는 다르게 나올 것이다. 이 경우, 나는 SQL서버가 같은 서버에 있는 환경에서 ASP 페이지들을 테스트 했고, 실제 운영에 대해서 일부는 고려되지 않은 점이 있다. 하드웨어는 델 워크스테이션(dual 1.4 GHz Xeon processors, 1 GB RAM, IDE drives)을 사용했다. 윈도우즈 서버 2003과 SQL 서버 2000(8.00.818)을 사용했다.

다른 크기의 데이터셋 또한 다른 결과가 나올 수도 있다. 나중에, 이번 것보다 몇배 더 큰 테이블에 같은 테스트들을 해보려한다.
You can find all of the files used in this article in our downloads section.

Classic ASP and MVC

메일링에... Classic ASP의 MVC 구현에 대한 글이 와서.. 잠시 드는 생각..

http://www.codeproject.com/Articles/585883/Classic-ASP-and-MVC

Classic ASP에서 MVC 구현 했다는게 일단은 대단하다만..

(저 프레임 웍은 나온지는 좀 된 걸로 기억한다)

굳이 Classic ASP에서 MVC가 필요한가? 란 생각이 든다..

ASP를 가장 ASP 답게 쓰는건 그냥.. raw 코딩이 아닐까?

페이징 기법 같은 거만 신경을 쓰면, 저런 MVC 구현은 오히려

문제만 복잡해 진다는 생각이다..

ASP 의 기능이 모자라다고 생각하면..

ASP Xtreme Evolution 에서 필요한 것을 찾을 수 있을 것이다..

사실.. 그리고.. 난..


난 MVC가 싫어요~~~~

몇년전에 네이년 면접시에 프레임웍 사용에 대한 견해를 얘기해보라고 해서..


개발자 바보 만드는 MVC 따위는 사라져야 한다!

패턴화로 인해 스스로 해결하는 능력이 부족해 진다!


라고 해서.. 짤렸음!

끗!

2016년 6월 22일 수요일

DB 쿼리문으로 ASP, JSP 구문 생성기

난 아주 게으른 놈이다..

그래서.. 이딴거 만든다..

난 sql += "어쩌구 저쩌구.."; 이딴거도 치기 싫단 말이다!!

그래서 만들었슴다..

https://github.com/jaymz9634/codegen/blob/master/code.html

쿼리문 넣고 생성하면..

자동 구문 생성~~~

짜잔~~~

추후 내맘대로 기능 추가.

혹시나 요청 있는 분들.. 코멘트 달아주셈..

2015년 12월 16일 수요일

Class ASP 윈도우 메시지 서비스 정리한거..

하도 까먹어서 잘 된거 퍼옴..

Windows 2000 에서 발송

Set ObjMail        = Server.CreateObject("CDONTS.NEWMAIL")                                   ' 개체생성
ObjMail.Subject  = "테스트 메일 제목입니다."                                                             ' 메일제목
ObjMail.From     = "진실세상"                                                  ' 보내는사람 (형식 : 표시할이름<메일주소>) 
ObjMail.To         = "받는사람1;받는사람2"      ' 받는사람
ObjMail.Cc         = "참조1;참조2"                    ' 참조 
ObjMail.Bcc       = "숨은참조1;참조2"           ' 숨은참조  
ObjMail.Body     = "HTML 테스트 내용입니다."                     ' 메일 내용                           
ObjMail.MailFormat = 0                                                                                          ' HTML일떄 0, 일반 Text일때 1 으로 설정한다.
ObjMail.BodyFormat = 0                                                                                         ' HTML일떄 0, 일반 Text일때 1 으로 설정한다.
ObjMail.AttachFile "d:/Test/test.jpg"                                                                        ' 첨부파일 
ObjMail.Send
Set ObjMail = Nothing
%> 
※ 참고 : To, Cc, Bcc는 세미콜론(;)으로 연결해서 여러명에게 발송 가능합니다. (형식 : 표시할이름<메일주소>;표시할이름<메일주소>;....) 
             파일명을 변경해서 보낼경우 (ObjMail.AttachFile "d:/Test/test.jpg", "1.jpg")

Windows 2003 에서 발송1. 기본구조

Set ObjMail     = Server.CreateObject("CDO.Message")                                             ' 개체생성
ObjMail.Subject = "테스트 메일 제목입니다."                                                             ' 메일제목
ObjMail.From     = "진실세상"                                                  ' 보내는사람 (형식 : 표시할이름<메일주소>) 
ObjMail.To         = "받는사람1;받는사람2"      ' 받는사람
ObjMail.Cc         = "참조1;참조2"                    ' 참조 
ObjMail.Bcc       = "숨은참조1;참조2"           ' 숨은참조  
ObjMail.Body     = "HTML 테스트 내용입니다."                     ' 메일 내용                           
ObjMail.TextBody = "테스트 메일 내용입니다."                                                          ' 텍스트 메일 내용                           
ObjMail.Send
Set ObjMail = Nothing
%>
2. 내용을 HTML 형식으로 발송할 경우
ObjMail.TextBody 를 다음으로 변경하면 됩니다.
ObjMail.HTMLBody = "HTML 테스트 내용입니다." 
ObjMail.HTMLBodyPart.Charset = "euc-kr"                                                              ' 알맞은 Charset 을 명시
3. 내용을 웹페이지나 파일을 읽어서 발송하는 경우
ObjMail.TextBody 를 다음으로 변경하면 됩니다.
ObjMail.CreateMHTMLBody "http://truepia.tistory.com/"               ' 웹페이지를 보내는 경우
ObjMail.CreateMHTMLBody "file:///d:/Test/test.html"                  ' 서버에 있는 파일을 보내는 경우
서버에 있는 파일을 보내는 경우 확장자가 html 또는 htm 이여야만 합니다.
아... 또 실험정신발동~!!!!!
zip 파일을 확장자만 바꿔서 보내볼까? 해서 보내보니 역시 보내집니다. ㅋㅋ (보안상 문제가 될 소지가 있어보입니다.)
4. 첨부파일을 보내는 경우
다음 코드를 ObjMail.Send 코드 위에 추가해 주시면 됩니다.
ObjMail.AddAttachment "http://icon.daum-img.net/top/cms/logo/2008/12/nano46827545881876601.gif"  ' URL형식의 첨부파일도 가능함.
ObjMail.AddAttachment "d:/Test/test.jpg"           
단 위에 명시한 파일 경로는 서버에 있는 파일경로를 의미합니다.
클라이언트의 파일을 전송하려면 먼저 클라이언트의 파일을 서버로 업로드 하고 서버에 저장된 파일 경로를 명시해 줘야 합니다.
여러개의 첨부파일을 보낼경우 계속적으로 ObjMail.AddAttachment 로 추가해 주면 됩니다.
5. 로컬이 아닌 원격지 메일 서버를 사용하여 메일을 발송하는 경우
CDO.Configuration.Fields 는 여러개가 있지만 가장 대표적으로 쓰는것만 살펴보겠습니다.

Set ObjMail     = Server.CreateObject("CDO.Message")                                ' 개체생성
Set ObjConf     = Server.CreateObject("CDO.Configuration")                          ' 개체생성
Set Flds = ObjConf.Fields
' 1:local SMTP로 발송(pickup directory), 2:원격지 SMTP로 발송
Flds.Item("http://schemas.microsoft.com/cdo/configuration/sendusing") = 2          
Flds.Item("http://schemas.microsoft.com/cdo/configuration/smtpserver") = "mail.domain.com"  ' 도메인 or IP
Flds.Item("http://schemas.microsoft.com/cdo/configuration/smtpserverport") = 25                     ' 원격지 SMTP Port 번호
Flds.Item("http://schemas.microsoft.com/cdo/configuration/sendusername") = "username"       ' 원격지 SMTP에 인증이 있을경우 계정
Flds.Item("http://schemas.microsoft.com/cdo/configuration/sendpassword") = "passwd"          ' 원격지 SMTP에 인증이 있을경우 비밀번호
Flds.Update
Set ObjMail.Configuration = ObjConf
ObjMail.Subject = "테스트 메일 제목입니다."                                                             ' 메일제목
ObjMail.From     = "진실세상"                                                  ' 보내는사람 (형식 : 표시할이름<메일주소>) 
ObjMail.To         = "받는사람1;받는사람2"      ' 받는사람
ObjMail.Cc         = "참조1;참조2"                    ' 참조 
ObjMail.Bcc       = "숨은참조1;참조2"           ' 숨은참조  
ObjMail.TextBody = "테스트 메일 내용입니다."                                                          ' 텍스트 메일 내용   
ObjMail.AddAttachment "d:/Test/test.jpg"                              
ObjMail.Send
Set ObjMail = Nothing
%> 
단 mail.domain.com 서버의 SMTP에서 소스가 있는 서버에서 접근할 수 있도록 릴레이 제한이 풀려있어야 합니다.
그렇지 않을 경우 ObjMail.Send 라인에서 에러가 발생합니다.

6. 배달 확인 / 읽음 확인 구현 방법
Set ObjMail     = Server.CreateObject("CDO.Message")                                ' 개체생성
Set ObjConf     = Server.CreateObject("CDO.Configuration")                          ' 개체생성

Set Flds = ObjConf.Fields
' 1:local SMTP로 발송(pickup directory), 2:원격지 SMTP로 발송
Flds.Item("http://schemas.microsoft.com/cdo/configuration/sendusing") = 2          
Flds.Item("http://schemas.microsoft.com/cdo/configuration/smtpserver") = "mail.domain.com"  ' 도메인 or IP
Flds.Item("http://schemas.microsoft.com/cdo/configuration/smtpserverport") = 25                     ' 원격지 SMTP Port 번호
Flds.Item("http://schemas.microsoft.com/cdo/configuration/sendusername") = "username"       ' 원격지 SMTP에 인증이 있을경우 계정
Flds.Item("http://schemas.microsoft.com/cdo/configuration/sendpassword") = "passwd"          ' 원격지 SMTP에 인증이 있을경우 비밀번호
Flds.Update
Set ObjMail.Configuration = ObjConf
ObjMail.Subject = "테스트 메일 제목입니다."                                                             ' 메일제목
ObjMail.From     = "진실세상"                                                  ' 보내는사람 (형식 : 표시할이름<메일주소>) 
ObjMail.To         = "받는사람1"                                                 ' 받는사람
ObjMail.TextBody = "테스트 메일 내용입니다."                                                          ' 텍스트 메일 내용   
ObjMail.DSNOptions = 14' 이 메시지의 배달 상태 확인(delivery status notification:DSN)값으로 14는 배달 성공, 실패 및 지연시 확인메시지 생성
' 0 : 확인메세지 생성 안함
' 1 : 확인메세지 생성 안함
' 2 : 배달 실패시 확인메시지 생성
' 4 : 배달 성공시 확인메시지 생성
' 8 : 배달 지연시 확인메시지 생성
'14 :  배달 성공, 실패 및 지연시 확인메시지 생성
ObjMail.Fields("urn:schemas:mailheader:return-receipt-to")= "진실세상"
' 받는 사람이 이 메시지를 열었을 때 읽음 확인 메시지가 여기에서 지정된 사람에게 보내집니다.
ObjMail.Send
Set ObjMail = Nothing
%>

[낯선] 2026.06.20 이태원 MZK MuzikBar 공연

간만의 공연 이태원 비틀즈가 이전하여 MZK MuzikBar 로 새 단장 오픈을 한 뒤로 2번째 공연 새 베이시스트가 합류한 뒤로 첫 공연이기도 함.. 이래저래 틀린 부분도 많지만.. 그래도 간만에 그럭저럭 잘했다.. https://www.youtub...