Rev. 2.73

최근에 날라드는 스팸 트랙백들 살펴보면, 패턴을 이용한 차단을 피하기 위해 서로 다른 내용의 동일 스팸 트랙백을 생성하며, 하나의 URL에서 다량으로 수집한 트랙백 주소에 단 시간내에 무작위로 발사하도록 되어있다. 이 것은 단 10분만에 100여건을 성공시키기도 한다. OR2 패치를 적용하면 영문으로 구성된 트랙백이야 차단된다 하더라도 영문에 2바이트 특수문자를 사용하거나 영문이 아닌 외국어가 들어간 스팸 트랙백에 대한 차단은 아직 무방비 상태다. 그래서 아래와 같은 꼼수를 만들어 보았다.

스팸의 인식 조건은 지정된 시간 안에 지정된 량의 수신 트랙백에서 내용(사이트명, URL, 타이틀, IP)이 동일한 트랙백을 발견하면 스팸으로 간주하고 해당 수신을 거부한다. 이것으로 모두 차단할 수는 없겠지만 대량으로 걸리는 스팸은 어느정도 차단할 수 있었다. 참고로, 정헌님의 트랙백스팸 추적과 함께 사용하면 더욱 든든하다.

rserver.php 파일의 43열 부근을 아래와 같이 수정한다.

$result = is_spam("2", "1", $title);
if (!$result) $result = is_spam("2", "2", $url);
if (!$result) $result = is_spam("2", "3", $excerpt);

/* TrackbackBlocker */
if (!$result) {
$r_limit = 5; // 검사할 최근 트랙백 설정, 기본 5개
$r_time = 1; // 시간 설정, 기본 1시간
$r_sql = "select site, url, title, regdate, ip from t3_".$dbid."_trackback order by regdate desc limit $r_limit";
$r_result = @mysql_query($r_sql);
while(list($r_site, $r_url, $r_title, $r_regdate, $r_ip) = @mysql_fetch_array($r_result)) {
if (time() - $r_regdate < 60*60*$r_time && ($REMOTE_ADDR == $r_ip && ($url == $r_url && $title == $r_title && $blog_name == $r_site && $r_ip))) {
$result = true;
break;
}
}
}
/* end of TrackbackBlocker */

if (!$no || !$url || !$title || !$blog_name || !$excerpt || $result || (is_eng_only($title) && is_eng_only($excerpt))) {
echo "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?".">\n<response>\n<error>1</error>\n<message>Incomplete Information</message>\n</response>";
exit;
}

※ 오류, 버그, 개선 등과 관련된 피드백은 이곳에 댓글 및 트랙백으로 남겨주십시오.
※ 검증된 태스트과정을 거치지 않아 오작동을 일으킬 수도 있습니다.
※ 라이센스는 지나가던 개에게 주었습니다.

Comments

최근 블로그에 Ajax를 반영하면서 맞닥드린 난관은 브라우저의 뒤로가기, 앞으로가기 그리고 새로고침 버튼입니다. Ajax로 불러들인 문서는 페이지를 로드하지 않고 필요한 부분만 가지고 오기 때문에 브라우저의 히스토리에 흔적을 남기지 않습니다. 덕분에 유저들이 가장 많이 사용하는 브라우저의 기본기능 중 하나인 뒤로가기 버튼이 제대로 작동하지 않아 혼란을 겪게되고, 방문객은 본의 아니게 사이트를 떠나게 될수도 있다고 경고하는 출판물을 접한적이 있습니다.

이 문제를 해결하고자 이곳 저곳을 싸돌아 다니던 중 The Rails-spinoffs Archives에 'Ajax Bookmarks & Browser Navigation'라는 주제로 작성된 Ajax사용시 백버튼 이슈 중에서 Siegfried Puchbauer씨가 작성한 내용과 코드를 발견했습니다. Prototype의 Ajax.request 또는 Ajax.update를 사용할 때 해시 히스토리를 만들어 앞으로/뒤로 가기 문제를 해결하고 있습니다. 이것은 구글이 즐겨 사용하는 Ajax트릭과 흡사합니다.(예를 들면 피카사 웹앨범)

// Author: Siegfried Puchbauer <rails-spinoffs@lists.rubyonrails.org>
Ajax.History = {
    initialize: function(options) {
        this.options = Object.extend({
            interval: 200
        },options||{});
        this.callback = this.options.callback || Prototype.emtpyfunction;
        if(navigator.userAgent.toLowerCase().indexOf('msie') > 0)
            this.locator = new Ajax.History.Iframe('ajaxHistoryHandler', this.options.iframeSrc);
        else
            this.locator = new Ajax.History.Hash();
        this.currentHash = '';
        this.locked = false;
    },
    add: function(hash) {
        this.locked = true;
        clearTimeout(this.timer);
        this.currentHash = hash;
        this.locator.setHash(hash);
        this.timer = setTimeout(this.checkHash.bind(this), this.options.interval);
        this.locked = false;
    },
    checkHash: function(){
        if(!this.locked){
            var check = this.locator.getHash();

            if(check != this.currentHash){
                this.callback(check);
                this.currentHash = check;
            }
        }
        this.timer = setTimeout(this.checkHash.bind(this), this.options.interval);
    },
    getBookmark: function(){
        return this.locator.getBookmark();
    }
};

// Hash Handler for IE (Tested with IE6)
Ajax.History.Iframe = Class.create();
Ajax.History.Iframe.prototype = {
    initialize: function(id, src) {
        this.url = '';
        this.id = id || 'ajaxHistoryHandler';
        this.src = src || '';
        document.write('<iframe src="'+this.src+'" id="'+this.id+'"name="'+this.id+'" style="display: none;" ></iframe>');
    },
    setHash: function(hash){
        try {
            $(this.id).setAttribute('src', this.src + '?' + hash);
        }catch(e) {}
        window.location.href = this.url + '#' + hash;
    },
    getHash: function(){
        try {
            return (document.frames[this.id].location.href||'?').split('?')[1];
        }catch(e){ return ''; }
    },
    getBookmark: function(){
        try{
            return window.location.href.split('#')[1]||'';
        }catch(e){ return ''; }
    }
};

// Hash Handler for a modern browser (tested with firefox 1.5)
Ajax.History.Hash = Class.create();
Ajax.History.Hash.prototype = {
    initialize: function(){
    },
    setHash: function(hash){
        window.location.hash = hash;
    },
    getHash: function(){
        return window.location.hash.substring(1)||'';
    },
    getBookmark: function(){
        try{
            return window.location.hash.substring(1)||'';
        }catch(e){ return ''; }
    }
};

위와 같은 코드를 로드한 후 아래처럼 콜백 메서드를 히스토리 핸들러에 Ajax 요청을 보낼 수 있습니다.

var historyUpdate = function(hash) {
	new Ajax.Request(
	....
}

Prototype의 Ajax.Request 또는 Ajax.Updater를 사용할 때 onSuccess 및 onComplete 메서드에 히스토리를 아래와 같이 추가할 수 있습니다.

var handleSuccess = function(request){
    //... update DOM or smth like that
    Ajax.History.add(request.options.parameters)
    ...
}

그리고 페이지 푸터에 아래의 코드를 실행하도록 합니다.

var historyHandler = Ajax.History.initialize({
    callback: historyUpdate,
    iframeSrc: '_blank.html' // 비어있는 임시 html 페이지 경로
});

해시가 추가된 주소로 접근했을 때의 액션은 정의되지 않았지만, Scriptaculous 프레임웍 기반 구찌(GUCCI)는 해시를 가진 상품주소로 접근한 경우 해당 상품정보로 멋지게 연결하고 있습니다. 참고해 봅시다.

2008-10-2 여러분의 요청으로 간단한 예제파일을 만들어 첨부합니다.

Ajax.History.Example.zip Ajax.History.Example.zip (2.1 KB)

Comments

Ajax가 성행하면서 자바스크립트로 DOM을 직접 다뤄야 하는 일이 잦아졌다. 만약 자바스크립트를 이용해 노드를 대량으로 생산해야하는 임무가 주어졌다면 어떻게 처리하는 것이 효과적일까? 실제로 이와 비슷한 프로세스를 수행하는 이미지 리플랙터의 소스코드를 분석하고 이를 개선해 보자. 참고로, 이미지 리플랙터는 이미지의 반사된 잔상효과를 만들어주는 이펙트 라이브러리이다.

var Reflector = {
    reflect: function(element){
        element = $(element);
        var options = $H({
            amount: 1/3,
            opacity: 1/3
        }).merge(arguments[1] || {});
        var p = element.parentNode, n = element.nextSibling;
        var d = 1.0/(element.height*options.amount);
        (element.height*options.amount).times( function(line){
            var h = Builder.node('div',{style:'height:1px;overflow:hidden;'},
            [Builder.node('img',{src:element.src,
                style:'margin-top:-'+(element.height-line-1)+'px'
            })]);
            p.insertBefore(h,n);
            $(h).setOpacity((1-d*line)*options.opacity);
        });
    }
};

// Run
Reflector.reflect('img_id',{ amount:1/5, opacity:1/4 });

위 코드는 이미지 리플랙터의 원본 코드로서 실행되면 브라우저가 먹통이 되는 증상을 보인이다. Scriptaculous 프레임웍의 builder.js 라이브러리를 사용하는 Builder 메서드를 사용해서 높이 1px짜리 DIV와 IMG태그를 대량으로 생산하고 있다. Builder메서드는 엘리먼트를 생성하기 위한 document.createElement()와 innerHTML, Prototype의 setStyle 등을 간단하게 사용할 수 있게한다. 즉 createElement와 setStyle 메서드가 루프를 돌면서 브라우저에게 마비증상을 선사(?)하는 것이다.

var Reflector = {
    reflect: function(element){
        element = $(element);
        var options = $H({
            amount: 1/3,
            opacity: 1/3
        }).merge(arguments[1] || {});
        var p = element.parentNode, h = '';
        var d = 1.0/(element.height*options.amount);
        var m = document.createElement('DIV');
        for(var i=0; i < element.height*options.amount; i++){
            h += '<div style="height:1px; overflow:hidden;'+this.setOpacity((1-d*i)*options.opacity)+'">';
            h += '<img src="'+element.src+'" style="margin-top:-'+(element.height-i-1)+'px" alt="" /></div>';
        }
        m.innerHTML = h;
        p.insertBefore(m, element.nextSibling);
    },
    setOpacity: function(value) {
        var style = 'opacity:'+value;  
        if(Prototype.Browser.IE) style = 'filter:alpha(opacity='+value*100+')';
        return style;
    }
};

이것은 innerHTML에 직접 코드를 삽입하는 방식으로 변경한 것이다. 이미지 하단에 들어갈 'DIV'엘리먼트를 하나만 생성하고 루프되는 코드를 직접 작성하여 생성한 DIV에 innerHTML로 넣어준다. 결과는 완전히 동일하지만 소요되는 시간차이는 상당하다. createElement메서드로 640x360 이미지의 효과를 만들어 내는데 걸리는 시간은 파이어폭스가 2.25~4.563초, 익스플로러(IE7)가 12.19~15.18(HTML이 들고있는 주변 태그에 영향을 크게 받는다. 상단의 문법 하이라이트 코드를 수정했더니 결과가 확 바뀌었다.)초 정도가 소요된다. innerHTML 방식은 파이어폭스가 0.07~0.11초, 익스플로러가 0.04~0.11초 정도로 환경에 따라 다르겠지만 약 30 ~ 50배 가까이 소요시간을 단축하는 효과를 볼 수 있다. 생성하는 노드의 수가 많으면 많을 수록 더욱 큰 차이를 보인다. 아래의 이미지를 직접 테스트해 보자.

Faux_Fur_wide_screen.jpg

최근에는 노드를 대량으로 생성하는 방식으로 엘리먼트의 코너를 둥글게하거나, 대각선을 그리는 등 굳이 이미지를 사용하지 않아도 자바스크립트를 사용하여 DOM을 동적으로 표현하는 꼼수들이 속속 선보이고 있다. 가뜩이나 새로선보인 IE7 RC1의 노드생성속도는 완전 쉣이다. 랙현상으로 인해 사용이 꺼려진다면 innerHTML로 DOM을 직접 그려보는 것도 좋은 대안이 될 수 있겠다.

Comments